View Javadoc

    1   /*
    2    * JEditTextArea.java - jEdit's text component
    3    * Copyright (C) 1999 Slava Pestov
    4    *
    5    * You may use and modify this package for any purpose. Redistribution is
    6    * permitted, in both source and binary form, provided that this notice
    7    * remains intact in all source distributions of this package.
    8    */
    9
    10  package org.syntax.jedit;
    11
    12  import java.awt.AWTEvent;
    13  import java.awt.BorderLayout;
    14  import java.awt.Component;
    15  import java.awt.Container;
    16  import java.awt.Dimension;
    17  import java.awt.Font;
    18  import java.awt.FontMetrics;
    19  import java.awt.Insets;
    20  import java.awt.LayoutManager;
    21  import java.awt.Rectangle;
    22  import java.awt.Toolkit;
    23  import java.awt.datatransfer.Clipboard;
    24  import java.awt.datatransfer.DataFlavor;
    25  import java.awt.datatransfer.StringSelection;
    26  import java.awt.event.ActionEvent;
    27  import java.awt.event.ActionListener;
    28  import java.awt.event.AdjustmentEvent;
    29  import java.awt.event.AdjustmentListener;
    30  import java.awt.event.ComponentAdapter;
    31  import java.awt.event.ComponentEvent;
    32  import java.awt.event.FocusEvent;
    33  import java.awt.event.FocusListener;
    34  import java.awt.event.InputEvent;
    35  import java.awt.event.KeyEvent;
    36  import java.awt.event.KeyListener;
    37  import java.awt.event.MouseAdapter;
    38  import java.awt.event.MouseEvent;
    39  import java.awt.event.MouseMotionListener;
    40  import java.awt.event.MouseWheelEvent;
    41  import java.awt.event.MouseWheelListener;
    42  import java.lang.ref.WeakReference;
    43
    44  import javax.swing.JComponent;
    45  import javax.swing.JPopupMenu;
    46  import javax.swing.JViewport;
    47  import javax.swing.Scrollable;
    48  import javax.swing.SwingUtilities;
    49  import javax.swing.Timer;
    50  import javax.swing.event.CaretEvent;
    51  import javax.swing.event.CaretListener;
    52  import javax.swing.event.DocumentEvent;
    53  import javax.swing.event.DocumentListener;
    54  import javax.swing.event.EventListenerList;
    55  import javax.swing.text.BadLocationException;
    56  import javax.swing.text.Element;
    57  import javax.swing.text.Segment;
    58  import javax.swing.text.Utilities;
    59  import javax.swing.undo.AbstractUndoableEdit;
    60  import javax.swing.undo.CannotRedoException;
    61  import javax.swing.undo.CannotUndoException;
    62  import javax.swing.undo.UndoableEdit;
    63
    64  import org.syntax.jedit.tokenmarker.Token;
    65  import org.syntax.jedit.tokenmarker.TokenMarker;
    66
    67  import com.eviware.soapui.SoapUI;
    68
    69  /***
    70   * jEdit's text area component. It is more suited for editing program
    71   * source code than JEditorPane, because it drops the unnecessary features
    72   * (images, variable-width lines, and so on) and adds a whole bunch of
    73   * useful goodies such as:
    74   * <ul>
    75   * <li>More flexible key binding scheme
    76   * <li>Supports macro recorders
    77   * <li>Rectangular selection
    78   * <li>Bracket highlighting
    79   * <li>Syntax highlighting
    80   * <li>Command repetition
    81   * <li>Block caret can be enabled
    82   * </ul>
    83   * It is also faster and doesn't have as many problems. It can be used
    84   * in other applications; the only other part of jEdit it depends on is
    85   * the syntax package.<p>
    86   *
    87   * To use it in your app, treat it like any other component, for example:
    88   * <pre>JEditTextArea ta = new JEditTextArea();
    89   * ta.setTokenMarker(new JavaTokenMarker());
    90   * ta.setText("public class Test {\n"
    91   *     + "    public static void main(String[] args) {\n"
    92   *     + "        System.out.println(\"Hello World\");\n"
    93   *     + "    }\n"
    94   *     + "}");</pre>
    95   *
    96   * @author Slava Pestov
    97   * @version $Id$
    98   */
    99  public class JEditTextArea extends JComponent implements Scrollable
    100 {
    101 	/***
    102 	 * Adding components with this name to the text area will place
    103 	 * them left of the horizontal scroll bar. In jEdit, the status
    104 	 * bar is added this way.
    105 	 */
    106 	public final static String LEFT_OF_SCROLLBAR = "los";
    107
    108 	/***
    109 	 * Creates a new JEditTextArea with the default settings.
    110 	 */
    111 	public JEditTextArea()
    112 	{
    113 		this(TextAreaDefaults.getDefaults());
    114 	}
    115
    116 	/***
    117 	 * Creates a new JEditTextArea with the specified settings.
    118 	 * @param defaults The default settings
    119 	 */
    120 	public JEditTextArea(TextAreaDefaults defaults)
    121 	{
    122 		// Enable the necessary events
    123 		enableEvents(AWTEvent.KEY_EVENT_MASK);
    124
    125 		// Initialize some misc. stuff
    126 		painter = new TextAreaPainter(this,defaults);
    127 		documentHandler = new DocumentHandler();
    128 		listenerList = new EventListenerList();
    129 		caretEvent = new MutableCaretEvent();
    130 		lineSegment = new Segment();
    131 		bracketLine = bracketPosition = -1;
    132 		blink = true;
    133
    134
    135 		setAutoscrolls( true );
    136
    137 		// Initialize the GUI
    138
    139 		//setLayout(new ScrollLayout());
    140 		//add(CENTER,painter);
    141 		setLayout( new BorderLayout() );
    142 		add( painter, BorderLayout.CENTER );
    143 //		setBackground( Color.WHITE );
    144 //		setBorder( null );
    145 		//add(RIGHT,vertical = new JScrollBar(JScrollBar.VERTICAL));
    146 		//add(BOTTOM,horizontal = new JScrollBar(JScrollBar.HORIZONTAL));
    147
    148 		// Add some event listeners
    149 		//vertical.addAdjustmentListener(new AdjustHandler());
    150 		//horizontal.addAdjustmentListener(new AdjustHandler());
    151 		painter.addComponentListener(new ComponentHandler());
    152 		painter.addMouseListener(new MouseHandler());
    153 		painter.addMouseMotionListener(new DragHandler());
    154 		addFocusListener(new FocusHandler());
    155
    156 		// Load the defaults
    157 		setInputHandler(defaults.inputHandler);
    158 		setDocument(defaults.document);
    159 		editable = defaults.editable;
    160 		caretVisible = defaults.caretVisible;
    161 		caretBlinks = defaults.caretBlinks;
    162 	//	electricScroll = defaults.electricScroll;
    163
    164 		popup = defaults.popup;
    165
    166 		// We don't seem to get the initial focus event?
    167 		focusedComponentRef = new WeakReference<JEditTextArea>( this );
    168
    169 		addMouseWheelListener( new MouseWheelListener(){
    170
    171 			public void mouseWheelMoved(MouseWheelEvent e)
    172 			{
    173 				if( (e.getModifiers() & Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()) ==
    174 					Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() )
    175 				{
    176 				   int caretLine = getCaretLine();
    177 //				   int caretPosition = getCaretPosition();
    178
    179 				   int newLine = caretLine + e.getWheelRotation();
    180 				   if( newLine < 0 ) newLine = 0;
    181 				   else if( newLine > getLineCount()-1) newLine = getLineCount()-1;
    182 				   int newPos = getLineStartOffset( newLine );
    183
    184 				   setCaretPosition( newPos );
    185 				}
    186 				else
    187 				{
    188 					Rectangle rect = getVisibleRect();
    189 					rect.setLocation( (int) rect.getX(),
    190 							(int)rect.getY() + painter.getFontMetrics().getHeight()*3*e.getWheelRotation());
    191 					scrollRectToVisible( rect );
    192 				}
    193 			}});
    194 	}
    195
    196 	/***
    197 	 * Returns if this component can be traversed by pressing
    198 	 * the Tab key. This returns false.
    199 	 */
    200 	public final boolean isManagingFocus()
    201 	{
    202 		return true;
    203 	}
    204
    205 	/***
    206 	 * Returns the object responsible for painting this text area.
    207 	 */
    208 	public final TextAreaPainter getPainter()
    209 	{
    210 		return painter;
    211 	}
    212
    213 	/***
    214 	 * Returns the input handler.
    215 	 */
    216 	public final InputHandler getInputHandler()
    217 	{
    218 		return inputHandler;
    219 	}
    220
    221 	/***
    222 	 * Sets the input handler.
    223 	 * @param inputHandler The new input handler
    224 	 */
    225 	public void setInputHandler(InputHandler inputHandler)
    226 	{
    227 		this.inputHandler = inputHandler;
    228 	}
    229
    230 	/***
    231 	 * Returns true if the caret is blinking, false otherwise.
    232 	 */
    233 	public final boolean isCaretBlinkEnabled()
    234 	{
    235 		return caretBlinks;
    236 	}
    237
    238 	/***
    239 	 * Toggles caret blinking.
    240 	 * @param caretBlinks True if the caret should blink, false otherwise
    241 	 */
    242 	public void setCaretBlinkEnabled(boolean caretBlinks)
    243 	{
    244 		this.caretBlinks = caretBlinks;
    245 		if(!caretBlinks)
    246 			blink = false;
    247
    248 		painter.invalidateSelectedLines();
    249 	}
    250
    251 	/***
    252 	 * Returns true if the caret is visible, false otherwise.
    253 	 */
    254 	public final boolean isCaretVisible()
    255 	{
    256 		return (!caretBlinks || blink) && caretVisible;
    257 	}
    258
    259 	/***
    260 	 * Sets if the caret should be visible.
    261 	 * @param caretVisible True if the caret should be visible, false
    262 	 * otherwise
    263 	 */
    264 	public void setCaretVisible(boolean caretVisible)
    265 	{
    266 		this.caretVisible = caretVisible;
    267 		blink = true;
    268
    269 		painter.invalidateSelectedLines();
    270 	}
    271
    272 	/***
    273 	 * Blinks the caret.
    274 	 */
    275 	public final void blinkCaret()
    276 	{
    277 		if(caretBlinks && caretVisible)
    278 		{
    279 			blink = !blink;
    280 			painter.invalidateSelectedLines();
    281 		}
    282 		else
    283 			blink = true;
    284 	}
    285
    286 	/***
    287 	 * Returns the number of lines from the top and button of the
    288 	 * text area that are always visible.
    289 	 */
    290 	/*public final int getElectricScroll()
    291 	{
    292 		return electricScroll;
    293 	}*/
    294
    295 	/***
    296 	 * Sets the number of lines from the top and bottom of the text
    297 	 * area that are always visible
    298 	 * @param electricScroll The number of lines always visible from
    299 	 * the top or bottom
    300 	 */
    301 	/*
    302 	public final void setElectricScroll(int electricScroll)
    303 	{
    304 		this.electricScroll = electricScroll;
    305 	}*/
    306
    307 	/***
    308 	 * Updates the state of the scroll bars. This should be called
    309 	 * if the number of lines in the document changes, or when the
    310 	 * size of the text area changes.
    311 	 */
    312 	public void updateScrollBars()
    313 	{
    314 		revalidate();
    315 	}
    316
    317 	/***
    318 	 * Returns the line displayed at the text area's origin.
    319 	 */
    320 	public final int getFirstLine()
    321 	{
    322 		return firstLine;
    323 	}
    324
    325 	/***
    326 	 * Sets the line displayed at the text area's origin without
    327 	 * updating the scroll bars.
    328 	 */
    329 	public void setFirstLine(int firstLine)
    330 	{
    331 		if(firstLine == this.firstLine)
    332 			return;
    333 //		int oldFirstLine = this.firstLine;
    334 		this.firstLine = firstLine;
    335 	//	if(firstLine != vertical.getValue())
    336 		updateScrollBars();
    337 		painter.repaint();
    338 	}
    339
    340 	/***
    341 	 * Returns the number of lines visible in this text area.
    342 	 */
    343 	public final int getVisibleLines()
    344 	{
    345 		return visibleLines;
    346 	}
    347
    348 	/***
    349 	 * Recalculates the number of visible lines. This should not
    350 	 * be called directly.
    351 	 */
    352 	public final void recalculateVisibleLines()
    353 	{
    354 		if(painter == null)
    355 			return;
    356 		int height = painter.getHeight();
    357 		int lineHeight = painter.getFontMetrics().getHeight();
    358 		visibleLines = height / lineHeight;
    359 		updateScrollBars();
    360 	}
    361
    362 	/***
    363 	 * Returns the horizontal offset of drawn lines.
    364 	 *//*
    365 	public final int getHorizontalOffset()
    366 	{
    367 		return horizontalOffset;
    368 	}*/
    369
    370 	/***
    371 	 * Sets the horizontal offset of drawn lines. This can be used to
    372 	 * implement horizontal scrolling.
    373 	 * @param horizontalOffset offset The new horizontal offset
    374 	 *//*
    375 	public void setHorizontalOffset(int horizontalOffset)
    376 	{
    377 		if(horizontalOffset == this.horizontalOffset)
    378 			return;
    379 		this.horizontalOffset = horizontalOffset;
    380 //		if(horizontalOffset != horizontal.getValue())
    381 		updateScrollBars();
    382 		painter.repaint();
    383 	}*/
    384
    385 	/***
    386 	 * A fast way of changing both the first line and horizontal
    387 	 * offset.
    388 	 * @param firstLine The new first line
    389 	 * @param horizontalOffset The new horizontal offset
    390 	 * @return True if any of the values were changed, false otherwise
    391 	 */
    392 	/*
    393 	public void setOrigin(int firstLine, int horizontalOffset)
    394 	{
    395 		boolean changed = false;
    396 		int oldFirstLine = this.firstLine;
    397
    398 		if(horizontalOffset != this.horizontalOffset)
    399 		{
    400 			this.horizontalOffset = horizontalOffset;
    401 			changed = true;
    402 		}
    403
    404 		if(firstLine != this.firstLine)
    405 		{
    406 			this.firstLine = firstLine;
    407 			changed = true;
    408 		}
    409
    410 		if(changed)
    411 		{
    412 		scrollRectToVisible( new Rectangle( horizontalOffset,
    413 					firstLine*painter.getFontMetrics().getHeight(), 1, 1));
    414
    415 			updateScrollBars();
    416 			painter.repaint();
    417 		//}
    418
    419 	//	return changed;
    420 	}*/
    421
    422 	/***
    423 	 * Ensures that the caret is visible by scrolling the text area if
    424 	 * necessary.
    425 	 * @return True if scrolling was actually performed, false if the
    426 	 * caret was already visible
    427 	 */
    428 	public void scrollToCaret()
    429 	{
    430 		int line = getCaretLine();
    431 		int lineStart = getLineStartOffset(line);
    432 		int offset = Math.max(0,Math.min(getTabExpandedLineLength(line) - 1,
    433 			getCaretPosition() - lineStart));
    434
    435 		scrollTo(line,offset);
    436 	}
    437
    438 	/***
    439 	 * Ensures that the specified line and offset is visible by scrolling
    440 	 * the text area if necessary.
    441 	 * @param line The line to scroll to
    442 	 * @param offset The offset in the line to scroll to
    443 	 * @return True if scrolling was actually performed, false if the
    444 	 * line and offset was already visible
    445 	 */
    446 	public void scrollTo(int line, int offset)
    447 	{
    448 		// visibleLines == 0 before the component is realized
    449 		// we can't do any proper scrolling then, so we have
    450 		// this hack...
    451 		/*
    452 		if(visibleLines == 0)
    453 		{
    454 			setFirstLine(Math.max(0,line - electricScroll));
    455 			return true;
    456 		}
    457
    458 		int newFirstLine = firstLine;
    459 		int newHorizontalOffset = horizontalOffset;
    460
    461 		if(line < firstLine + electricScroll)
    462 		{
    463 			newFirstLine = Math.max(0,line - electricScroll);
    464 		}
    465 		else if(line + electricScroll >= firstLine + visibleLines)
    466 		{
    467 			newFirstLine = (line - visibleLines) + electricScroll + 1;
    468 			if(newFirstLine + visibleLines >= getLineCount())
    469 				newFirstLine = getLineCount() - visibleLines;
    470 			if(newFirstLine < 0)
    471 				newFirstLine = 0;
    472 		}*/
    473
    474 		int x = _offsetToX(line,offset);
    475 		int width = painter.getFontMetrics().charWidth('w');
    476 /*
    477 		if(x < 0)
    478 		{
    479 			newHorizontalOffset = Math.min(0,horizontalOffset
    480 				- x + width + 5);
    481 		}
    482 		else if(x + width >= getVisibleRect().getWidth() )
    483 		{
    484 			newHorizontalOffset = horizontalOffset +
    485 				(x-(int)getVisibleRect().getWidth()) + width + 5;
    486 		}
    487 */
    488 		if( offset > 0 )
    489 			x += (width+5);
    490
    491 		int y = lineToY( line );
    492 		if( line > 0 )
    493 			y += 5;
    494
    495 		if( line > 0 )
    496 			line++;
    497
    498 		scrollRectToVisible( new Rectangle( x, y, 1, painter.getFontMetrics().getHeight()));
    499
    500 		updateScrollBars();
    501 		painter.repaint();
    502
    503 		//setOrigin(line, x);
    504 	}
    505
    506 	/***
    507 	 * Converts a line index to a y co-ordinate.
    508 	 * @param line The line
    509 	 */
    510 	public int lineToY(int line)
    511 	{
    512 		FontMetrics fm = painter.getFontMetrics();
    513 		return (line - firstLine) * fm.getHeight() - (fm.getLeading() + fm.getMaxDescent());
    514 	}
    515
    516 	public int getLineHeight()
    517 	{
    518 		FontMetrics fm = painter.getFontMetrics();
    519 		return fm.getHeight();
    520 	}
    521
    522 	/***
    523 	 * Converts a y co-ordinate to a line index.
    524 	 * @param y The y co-ordinate
    525 	 */
    526 	public int yToLine(int y)
    527 	{
    528 		FontMetrics fm = painter.getFontMetrics();
    529 		int height = fm.getHeight();
    530 		return Math.max(0,Math.min(getLineCount() - 1,
    531 			y / height + firstLine));
    532 	}
    533
    534 	/***
    535 	 * Converts an offset in a line into an x co-ordinate. This is a
    536 	 * slow version that can be used any time.
    537 	 * @param line The line
    538 	 * @param offset The offset, from the start of the line
    539 	 */
    540 	public final int offsetToX(int line, int offset)
    541 	{
    542 		// don't use cached tokens
    543 		painter.currentLineTokens = null;
    544 		return _offsetToX(line,offset);
    545 	}
    546
    547 	/***
    548 	 * Converts an offset in a line into an x co-ordinate. This is a
    549 	 * fast version that should only be used if no changes were made
    550 	 * to the text since the last repaint.
    551 	 * @param line The line
    552 	 * @param offset The offset, from the start of the line
    553 	 */
    554 	public int _offsetToX(int line, int offset)
    555 	{
    556 		TokenMarker tokenMarker = getTokenMarker();
    557
    558 		/* Use painter's cached info for speed */
    559 		FontMetrics fm = painter.getFontMetrics();
    560
    561 		getLineText(line,lineSegment);
    562
    563 		int segmentOffset = lineSegment.offset;
    564 		int x = 0; //-horizontalOffset;
    565
    566 		/* If syntax coloring is disabled, do simple translation */
    567 		if(tokenMarker == null)
    568 		{
    569 			lineSegment.count = offset;
    570 			return x + Utilities.getTabbedTextWidth(lineSegment,
    571 				fm,x,painter,0);
    572 		}
    573 		/* If syntax coloring is enabled, we have to do this because
    574 		 * tokens can vary in width */
    575 		else
    576 		{
    577 			Token tokens;
    578 			if(painter.currentLineIndex == line
    579 				&& painter.currentLineTokens != null)
    580 				tokens = painter.currentLineTokens;
    581 			else
    582 			{
    583 				painter.currentLineIndex = line;
    584 				tokens = painter.currentLineTokens
    585 					= tokenMarker.markTokens(lineSegment,line);
    586 			}
    587
    588 //			Toolkit toolkit = painter.getToolkit();
    589 			Font defaultFont = painter.getFont();
    590 			SyntaxStyle[] styles = painter.getStyles();
    591
    592 			for(;;)
    593 			{
    594 				byte id = tokens.id;
    595 				if(id == Token.END)
    596 				{
    597 					return x;
    598 				}
    599
    600 				if(id == Token.NULL)
    601 					fm = painter.getFontMetrics();
    602 				else
    603 					fm = styles[id].getFontMetrics(defaultFont);
    604
    605 				int length = tokens.length;
    606
    607 				if(offset + segmentOffset < lineSegment.offset + length)
    608 				{
    609 					lineSegment.count = offset - (lineSegment.offset - segmentOffset);
    610 					return x + Utilities.getTabbedTextWidth(
    611 						lineSegment,fm,x,painter,0);
    612 				}
    613 				else
    614 				{
    615 					lineSegment.count = length;
    616 					x += Utilities.getTabbedTextWidth(
    617 						lineSegment,fm,x,painter,0);
    618 					lineSegment.offset += length;
    619 				}
    620 				tokens = tokens.next;
    621 			}
    622 		}
    623 	}
    624
    625 	/***
    626 	 * Converts an x co-ordinate to an offset within a line.
    627 	 * @param line The line
    628 	 * @param x The x co-ordinate
    629 	 */
    630 	public int xToOffset(int line, int x)
    631 	{
    632 		TokenMarker tokenMarker = getTokenMarker();
    633
    634 		/* Use painter's cached info for speed */
    635 		FontMetrics fm = painter.getFontMetrics();
    636
    637 		getLineText(line,lineSegment);
    638
    639 		char[] segmentArray = lineSegment.array;
    640 		int segmentOffset = lineSegment.offset;
    641 		int segmentCount = lineSegment.count;
    642
    643 		int width = 0; //-horizontalOffset;
    644
    645 		if(tokenMarker == null)
    646 		{
    647 			for(int i = 0; i < segmentCount; i++)
    648 			{
    649 				char c = segmentArray[i + segmentOffset];
    650 				int charWidth;
    651 				if(c == '\t')
    652 					charWidth = (int)painter.nextTabStop(width,i)
    653 						- width;
    654 				else
    655 					charWidth = fm.charWidth(c);
    656
    657 				if(painter.isBlockCaretEnabled())
    658 				{
    659 					if(x - charWidth <= width)
    660 						return i;
    661 				}
    662 				else
    663 				{
    664 					if(x - charWidth / 2 <= width)
    665 						return i;
    666 				}
    667
    668 				width += charWidth;
    669 			}
    670
    671 			return segmentCount;
    672 		}
    673 		else
    674 		{
    675 			Token tokens;
    676 			if(painter.currentLineIndex == line && painter
    677 				.currentLineTokens != null)
    678 				tokens = painter.currentLineTokens;
    679 			else
    680 			{
    681 				painter.currentLineIndex = line;
    682 				tokens = painter.currentLineTokens
    683 					= tokenMarker.markTokens(lineSegment,line);
    684 			}
    685
    686 			int offset = 0;
    687 //			Toolkit toolkit = painter.getToolkit();
    688 			Font defaultFont = painter.getFont();
    689 			SyntaxStyle[] styles = painter.getStyles();
    690
    691 			for(;;)
    692 			{
    693 				byte id = tokens.id;
    694 				if(id == Token.END)
    695 					return offset;
    696
    697 				if(id == Token.NULL)
    698 					fm = painter.getFontMetrics();
    699 				else
    700 					fm = styles[id].getFontMetrics(defaultFont);
    701
    702 				int length = tokens.length;
    703
    704 				for(int i = 0; i < length; i++)
    705 				{
    706 					char c = segmentArray[segmentOffset + offset + i];
    707 					int charWidth;
    708 					if(c == '\t')
    709 						charWidth = (int)painter.nextTabStop(width,offset + i)
    710 							- width;
    711 					else
    712 						charWidth = fm.charWidth(c);
    713
    714 					if(painter.isBlockCaretEnabled())
    715 					{
    716 						if(x - charWidth <= width)
    717 							return offset + i;
    718 					}
    719 					else
    720 					{
    721 						if(x - charWidth / 2 <= width)
    722 							return offset + i;
    723 					}
    724
    725 					width += charWidth;
    726 				}
    727
    728 				offset += length;
    729 				tokens = tokens.next;
    730 			}
    731 		}
    732 	}
    733
    734 	/***
    735 	 * Converts a point to an offset, from the start of the text.
    736 	 * @param x The x co-ordinate of the point
    737 	 * @param y The y co-ordinate of the point
    738 	 */
    739 	public int xyToOffset(int x, int y)
    740 	{
    741 		int line = yToLine(y);
    742 		int start = getLineStartOffset(line);
    743 		return start + xToOffset(line,x);
    744 	}
    745
    746 	/***
    747 	 * Returns the document this text area is editing.
    748 	 */
    749 	public final SyntaxDocument getDocument()
    750 	{
    751 		return document;
    752 	}
    753
    754 	/***
    755 	 * Sets the document this text area is editing.
    756 	 * @param document The document
    757 	 */
    758 	public void setDocument(SyntaxDocument document)
    759 	{
    760 		if(this.document == document)
    761 			return;
    762 		if(this.document != null)
    763 			this.document.removeDocumentListener(documentHandler);
    764 		this.document = document;
    765
    766 		if( getParent() != null )
    767 			document.addDocumentListener(documentHandler);
    768
    769 		select(0,0);
    770 		updateScrollBars();
    771 		painter.repaint();
    772 	}
    773
    774 	/***
    775 	 * Returns the document's token marker. Equivalent to calling
    776 	 * <code>getDocument().getTokenMarker()</code>.
    777 	 */
    778 	public final TokenMarker getTokenMarker()
    779 	{
    780 		return document.getTokenMarker();
    781 	}
    782
    783 	/***
    784 	 * Sets the document's token marker. Equivalent to caling
    785 	 * <code>getDocument().setTokenMarker()</code>.
    786 	 * @param tokenMarker The token marker
    787 	 */
    788 	public final void setTokenMarker(TokenMarker tokenMarker)
    789 	{
    790 		document.setTokenMarker(tokenMarker);
    791 	}
    792
    793 	/***
    794 	 * Returns the length of the document. Equivalent to calling
    795 	 * <code>getDocument().getLength()</code>.
    796 	 */
    797 	public final int getDocumentLength()
    798 	{
    799 		return document.getLength();
    800 	}
    801
    802 	/***
    803 	 * Returns the number of lines in the document.
    804 	 */
    805 	public final int getLineCount()
    806 	{
    807 		return document.getDefaultRootElement().getElementCount();
    808 	}
    809
    810 	/***
    811 	 * Returns the line containing the specified offset.
    812 	 * @param offset The offset
    813 	 */
    814 	public final int getLineOfOffset(int offset)
    815 	{
    816 		return document.getDefaultRootElement().getElementIndex(offset);
    817 	}
    818
    819 	/***
    820 	 * Returns the start offset of the specified line.
    821 	 * @param line The line
    822 	 * @return The start offset of the specified line, or -1 if the line is
    823 	 * invalid
    824 	 */
    825 	public int getLineStartOffset(int line)
    826 	{
    827 		Element lineElement = document.getDefaultRootElement()
    828 			.getElement(line);
    829 		if(lineElement == null)
    830 			return -1;
    831 		else
    832 			return lineElement.getStartOffset();
    833 	}
    834
    835 	/***
    836 	 * Returns the end offset of the specified line.
    837 	 * @param line The line
    838 	 * @return The end offset of the specified line, or -1 if the line is
    839 	 * invalid.
    840 	 */
    841 	public int getLineEndOffset(int line)
    842 	{
    843 		Element lineElement = document.getDefaultRootElement()
    844 			.getElement(line);
    845 		if(lineElement == null)
    846 			return -1;
    847 		else
    848 			return lineElement.getEndOffset();
    849 	}
    850
    851 	/***
    852 	 * Returns the length of the specified line.
    853 	 * @param line The line
    854 	 */
    855 	public int getTabExpandedLineLength(int line)
    856 	{
    857 		Element lineElement = document.getDefaultRootElement()
    858 			.getElement(line);
    859 		if(lineElement == null)
    860 			return -1;
    861
    862 		int length = lineElement.getEndOffset() - lineElement.getStartOffset() - 1;
    863 		try
    864 		{
    865 			String txt = document.getText( lineElement.getStartOffset(), length );
    866
    867 			for( int c = 0; c < txt.length(); c++ )
    868 				if( txt.charAt( c ) == '\t' ) length += 7;
    869
    870 			return length;
    871 		}
    872 		catch( BadLocationException e )
    873 		{
    874 			e.printStackTrace();
    875 			return length;
    876 		}
    877 	}
    878
    879 	/***
    880 	 * Returns the length of the specified line.
    881 	 * @param line The line
    882 	 */
    883 	public int getLineLength(int line)
    884 	{
    885 		Element lineElement = document.getDefaultRootElement()
    886 			.getElement(line);
    887 		if(lineElement == null)
    888 			return -1;
    889
    890 		return lineElement.getEndOffset() - lineElement.getStartOffset() - 1;
    891 	}
    892
    893 	/***
    894 	 * Returns the entire text of this text area.
    895 	 */
    896 	public String getText()
    897 	{
    898 		try
    899 		{
    900 			return document.getText(0,document.getLength());
    901 		}
    902 		catch(BadLocationException bl)
    903 		{
    904 			SoapUI.logError( bl );
    905 			return null;
    906 		}
    907 	}
    908
    909 	/***
    910 	 * Sets the entire text of this text area.
    911 	 */
    912 	public synchronized void setText(String text)
    913 	{
    914 		try
    915 		{
    916 			document.beginCompoundEdit();
    917 			document.remove(0,document.getLength());
    918 			document.insertString(0,text,null);
    919
    920 			revalidate();
    921 		}
    922 		catch(BadLocationException bl)
    923 		{
    924 			SoapUI.logError( bl );
    925 		}
    926 		finally
    927 		{
    928 			document.endCompoundEdit();
    929 		}
    930 	}
    931
    932 	/***
    933 	 * Returns the specified substring of the document.
    934 	 * @param start The start offset
    935 	 * @param len The length of the substring
    936 	 * @return The substring, or null if the offsets are invalid
    937 	 */
    938 	public final String getText(int start, int len)
    939 	{
    940 		try
    941 		{
    942 			return document.getText(start,len);
    943 		}
    944 		catch(BadLocationException bl)
    945 		{
    946 			SoapUI.logError( bl );
    947 			return null;
    948 		}
    949 	}
    950
    951 	/***
    952 	 * Copies the specified substring of the document into a segment.
    953 	 * If the offsets are invalid, the segment will contain a null string.
    954 	 * @param start The start offset
    955 	 * @param len The length of the substring
    956 	 * @param segment The segment
    957 	 */
    958 	public final void getText(int start, int len, Segment segment)
    959 	{
    960 		try
    961 		{
    962 			document.getText(start,len,segment);
    963 		}
    964 		catch(BadLocationException bl)
    965 		{
    966 			SoapUI.logError( bl );
    967 			segment.offset = segment.count = 0;
    968 		}
    969 	}
    970
    971 	/***
    972 	 * Returns the text on the specified line.
    973 	 * @param lineIndex The line
    974 	 * @return The text, or null if the line is invalid
    975 	 */
    976 	public final String getLineText(int lineIndex)
    977 	{
    978 		int start = getLineStartOffset(lineIndex);
    979 		return getText(start,getLineEndOffset(lineIndex) - start - 1);
    980 	}
    981
    982 	/***
    983 	 * Copies the text on the specified line into a segment. If the line
    984 	 * is invalid, the segment will contain a null string.
    985 	 * @param lineIndex The line
    986 	 */
    987 	public final void getLineText(int lineIndex, Segment segment)
    988 	{
    989 		int start = getLineStartOffset(lineIndex);
    990 		getText(start,getLineEndOffset(lineIndex) - start - 1,segment);
    991 	}
    992
    993 	/***
    994 	 * Returns the selection start offset.
    995 	 */
    996 	public final int getSelectionStart()
    997 	{
    998 		return selectionStart;
    999 	}
    1000
    1001 	/***
    1002 	 * Returns the offset where the selection starts on the specified
    1003 	 * line.
    1004 	 */
    1005 	public int getSelectionStart(int line)
    1006 	{
    1007 		if(line == selectionStartLine)
    1008 			return selectionStart;
    1009 		else if(rectSelect)
    1010 		{
    1011 			Element map = document.getDefaultRootElement();
    1012 			int start = selectionStart - map.getElement(selectionStartLine)
    1013 				.getStartOffset();
    1014
    1015 			Element lineElement = map.getElement(line);
    1016 			int lineStart = lineElement.getStartOffset();
    1017 			int lineEnd = lineElement.getEndOffset() - 1;
    1018 			return Math.min(lineEnd,lineStart + start);
    1019 		}
    1020 		else
    1021 			return getLineStartOffset(line);
    1022 	}
    1023
    1024 	/***
    1025 	 * Returns the selection start line.
    1026 	 */
    1027 	public final int getSelectionStartLine()
    1028 	{
    1029 		return selectionStartLine;
    1030 	}
    1031
    1032 	/***
    1033 	 * Sets the selection start. The new selection will be the new
    1034 	 * selection start and the old selection end.
    1035 	 * @param selectionStart The selection start
    1036 	 * @see #select(int,int)
    1037 	 */
    1038 	public final void setSelectionStart(int selectionStart)
    1039 	{
    1040 		select(selectionStart,selectionEnd);
    1041 	}
    1042
    1043 	/***
    1044 	 * Returns the selection end offset.
    1045 	 */
    1046 	public final int getSelectionEnd()
    1047 	{
    1048 		return selectionEnd;
    1049 	}
    1050
    1051 	/***
    1052 	 * Returns the offset where the selection ends on the specified
    1053 	 * line.
    1054 	 */
    1055 	public int getSelectionEnd(int line)
    1056 	{
    1057 		if(line == selectionEndLine)
    1058 			return selectionEnd;
    1059 		else if(rectSelect)
    1060 		{
    1061 			Element map = document.getDefaultRootElement();
    1062 			int end = selectionEnd - map.getElement(selectionEndLine)
    1063 				.getStartOffset();
    1064
    1065 			Element lineElement = map.getElement(line);
    1066 			int lineStart = lineElement.getStartOffset();
    1067 			int lineEnd = lineElement.getEndOffset() - 1;
    1068 			return Math.min(lineEnd,lineStart + end);
    1069 		}
    1070 		else
    1071 			return getLineEndOffset(line) - 1;
    1072 	}
    1073
    1074 	/***
    1075 	 * Returns the selection end line.
    1076 	 */
    1077 	public final int getSelectionEndLine()
    1078 	{
    1079 		return selectionEndLine;
    1080 	}
    1081
    1082 	/***
    1083 	 * Sets the selection end. The new selection will be the old
    1084 	 * selection start and the bew selection end.
    1085 	 * @param selectionEnd The selection end
    1086 	 * @see #select(int,int)
    1087 	 */
    1088 	public final void setSelectionEnd(int selectionEnd)
    1089 	{
    1090 		select(selectionStart,selectionEnd);
    1091 	}
    1092
    1093 	/***
    1094 	 * Returns the caret position. This will either be the selection
    1095 	 * start or the selection end, depending on which direction the
    1096 	 * selection was made in.
    1097 	 */
    1098 	public final int getCaretPosition()
    1099 	{
    1100 		return (biasLeft ? selectionStart : selectionEnd);
    1101 	}
    1102
    1103 	/***
    1104 	 * Returns the caret line.
    1105 	 */
    1106 	public final int getCaretLine()
    1107 	{
    1108 		return (biasLeft ? selectionStartLine : selectionEndLine);
    1109 	}
    1110
    1111 	/***
    1112 	 * Returns the mark position. This will be the opposite selection
    1113 	 * bound to the caret position.
    1114 	 * @see #getCaretPosition()
    1115 	 */
    1116 	public final int getMarkPosition()
    1117 	{
    1118 		return (biasLeft ? selectionEnd : selectionStart);
    1119 	}
    1120
    1121 	/***
    1122 	 * Returns the mark line.
    1123 	 */
    1124 	public final int getMarkLine()
    1125 	{
    1126 		return (biasLeft ? selectionEndLine : selectionStartLine);
    1127 	}
    1128
    1129 	/***
    1130 	 * Sets the caret position. The new selection will consist of the
    1131 	 * caret position only (hence no text will be selected)
    1132 	 * @param caret The caret position
    1133 	 * @see #select(int,int)
    1134 	 */
    1135 	public final void setCaretPosition(int caret)
    1136 	{
    1137 		select(caret,caret);
    1138 	}
    1139
    1140 	/***
    1141 	 * Selects all text in the document.
    1142 	 */
    1143 	public final void selectAll()
    1144 	{
    1145 		select(0,getDocumentLength());
    1146 	}
    1147
    1148 	/***
    1149 	 * Moves the mark to the caret position.
    1150 	 */
    1151 	public final void selectNone()
    1152 	{
    1153 		select(getCaretPosition(),getCaretPosition());
    1154 	}
    1155
    1156 	/***
    1157 	 * Selects from the start offset to the end offset. This is the
    1158 	 * general selection method used by all other selecting methods.
    1159 	 * The caret position will be start if start &lt; end, and end
    1160 	 * if end &gt; start.
    1161 	 * @param start The start offset
    1162 	 * @param end The end offset
    1163 	 */
    1164 	public void select(int start, int end)
    1165 	{
    1166 		int newStart, newEnd;
    1167 		boolean newBias;
    1168 		if(start <= end)
    1169 		{
    1170 			newStart = start;
    1171 			newEnd = end;
    1172 			newBias = false;
    1173 		}
    1174 		else
    1175 		{
    1176 			newStart = end;
    1177 			newEnd = start;
    1178 			newBias = true;
    1179 		}
    1180
    1181 		if(newStart < 0 || newEnd > getDocumentLength())
    1182 		{
    1183 			throw new IllegalArgumentException("Bounds out of"
    1184 				+ " range: " + newStart + "," +
    1185 				newEnd);
    1186 		}
    1187
    1188 		// If the new position is the same as the old, we don't
    1189 		// do all this crap, however we still do the stuff at
    1190 		// the end (clearing magic position, scrolling)
    1191 		if(newStart != selectionStart || newEnd != selectionEnd
    1192 			|| newBias != biasLeft)
    1193 		{
    1194 			int newStartLine = getLineOfOffset(newStart);
    1195 			int newEndLine = getLineOfOffset(newEnd);
    1196
    1197 			if(painter.isBracketHighlightEnabled())
    1198 			{
    1199 				if(bracketLine != -1)
    1200 					painter.invalidateLine(bracketLine);
    1201 				updateBracketHighlight(end);
    1202 				if(bracketLine != -1)
    1203 					painter.invalidateLine(bracketLine);
    1204 			}
    1205
    1206 			painter.invalidateLineRange(selectionStartLine,selectionEndLine);
    1207 			painter.invalidateLineRange(newStartLine,newEndLine);
    1208
    1209 			document.addUndoableEdit(new CaretUndo(
    1210 				selectionStart,selectionEnd));
    1211
    1212 			selectionStart = newStart;
    1213 			selectionEnd = newEnd;
    1214 			selectionStartLine = newStartLine;
    1215 			selectionEndLine = newEndLine;
    1216 			biasLeft = newBias;
    1217
    1218 			fireCaretEvent();
    1219 		}
    1220
    1221 		// When the user is typing, etc, we don't want the caret
    1222 		// to blink
    1223 		blink = true;
    1224 		caretTimer.restart();
    1225
    1226 		// Disable rectangle select if selection start = selection end
    1227 		if(selectionStart == selectionEnd)
    1228 			rectSelect = false;
    1229
    1230 		// Clear the `magic' caret position used by up/down
    1231 		magicCaret = -1;
    1232
    1233 		scrollToCaret();
    1234 	}
    1235
    1236 	/***
    1237 	 * Returns the selected text, or null if no selection is active.
    1238 	 */
    1239 	public final String getSelectedText()
    1240 	{
    1241 		if(selectionStart == selectionEnd)
    1242 			return null;
    1243
    1244 		if(rectSelect)
    1245 		{
    1246 			// Return each row of the selection on a new line
    1247
    1248 			Element map = document.getDefaultRootElement();
    1249
    1250 			int start = selectionStart - map.getElement(selectionStartLine)
    1251 				.getStartOffset();
    1252 			int end = selectionEnd - map.getElement(selectionEndLine)
    1253 				.getStartOffset();
    1254
    1255 			// Certain rectangles satisfy this condition...
    1256 			if(end < start)
    1257 			{
    1258 				int tmp = end;
    1259 				end = start;
    1260 				start = tmp;
    1261 			}
    1262
    1263 			StringBuffer buf = new StringBuffer();
    1264 			Segment seg = new Segment();
    1265
    1266 			for(int i = selectionStartLine; i <= selectionEndLine; i++)
    1267 			{
    1268 				Element lineElement = map.getElement(i);
    1269 				int lineStart = lineElement.getStartOffset();
    1270 				int lineEnd = lineElement.getEndOffset() - 1;
    1271 				int lineLen = lineEnd - lineStart;
    1272
    1273 				lineStart = Math.min(lineStart + start,lineEnd);
    1274 				lineLen = Math.min(end - start,lineEnd - lineStart);
    1275
    1276 				getText(lineStart,lineLen,seg);
    1277 				buf.append(seg.array,seg.offset,seg.count);
    1278
    1279 				if(i != selectionEndLine)
    1280 					buf.append('\n');
    1281 			}
    1282
    1283 			return buf.toString();
    1284 		}
    1285 		else
    1286 		{
    1287 			return getText(selectionStart,
    1288 				selectionEnd - selectionStart);
    1289 		}
    1290 	}
    1291
    1292 	/***
    1293 	 * Replaces the selection with the specified text.
    1294 	 * @param selectedText The replacement text for the selection
    1295 	 */
    1296 	public void setSelectedText(String selectedText)
    1297 	{
    1298 		if(!editable)
    1299 		{
    1300 			throw new InternalError("Text component"
    1301 				+ " read only");
    1302 		}
    1303
    1304 		document.beginCompoundEdit();
    1305
    1306 		try
    1307 		{
    1308 			if(rectSelect)
    1309 			{
    1310 				Element map = document.getDefaultRootElement();
    1311
    1312 				int start = selectionStart - map.getElement(selectionStartLine)
    1313 					.getStartOffset();
    1314 				int end = selectionEnd - map.getElement(selectionEndLine)
    1315 					.getStartOffset();
    1316
    1317 				// Certain rectangles satisfy this condition...
    1318 				if(end < start)
    1319 				{
    1320 					int tmp = end;
    1321 					end = start;
    1322 					start = tmp;
    1323 				}
    1324
    1325 				int lastNewline = 0;
    1326 				int currNewline = 0;
    1327
    1328 				for(int i = selectionStartLine; i <= selectionEndLine; i++)
    1329 				{
    1330 					Element lineElement = map.getElement(i);
    1331 					int lineStart = lineElement.getStartOffset();
    1332 					int lineEnd = lineElement.getEndOffset() - 1;
    1333 					int rectStart = Math.min(lineEnd,lineStart + start);
    1334
    1335 					document.remove(rectStart,Math.min(lineEnd - rectStart,
    1336 						end - start));
    1337
    1338 					if(selectedText == null)
    1339 						continue;
    1340
    1341 					currNewline = selectedText.indexOf('\n',lastNewline);
    1342 					if(currNewline == -1)
    1343 						currNewline = selectedText.length();
    1344
    1345 					document.insertString(rectStart,selectedText
    1346 						.substring(lastNewline,currNewline),null);
    1347
    1348 					lastNewline = Math.min(selectedText.length(),
    1349 						currNewline + 1);
    1350 				}
    1351
    1352 				if(selectedText != null &&
    1353 					currNewline != selectedText.length())
    1354 				{
    1355 					int offset = map.getElement(selectionEndLine)
    1356 						.getEndOffset() - 1;
    1357 					document.insertString(offset,"\n",null);
    1358 					document.insertString(offset + 1,selectedText
    1359 						.substring(currNewline + 1),null);
    1360 				}
    1361 			}
    1362 			else
    1363 			{
    1364 				document.remove(selectionStart,
    1365 					selectionEnd - selectionStart);
    1366 				if(selectedText != null)
    1367 				{
    1368 					document.insertString(selectionStart,
    1369 						selectedText,null);
    1370 				}
    1371 			}
    1372 		}
    1373 		catch(BadLocationException bl)
    1374 		{
    1375 			SoapUI.logError( bl );
    1376 			throw new InternalError("Cannot replace"
    1377 				+ " selection");
    1378 		}
    1379 		// No matter what happends... stops us from leaving document
    1380 		// in a bad state
    1381 		finally
    1382 		{
    1383 			document.endCompoundEdit();
    1384 		}
    1385
    1386 		setCaretPosition(selectionEnd);
    1387 	}
    1388
    1389 	/***
    1390 	 * Returns true if this text area is editable, false otherwise.
    1391 	 */
    1392 	public final boolean isEditable()
    1393 	{
    1394 		return editable;
    1395 	}
    1396
    1397 	/***
    1398 	 * Sets if this component is editable.
    1399 	 * @param editable True if this text area should be editable,
    1400 	 * false otherwise
    1401 	 */
    1402 	public void setEditable(boolean editable)
    1403 	{
    1404 		this.editable = editable;
    1405 	}
    1406
    1407 	/***
    1408 	 * Returns the right click popup menu.
    1409 	 */
    1410 	public final JPopupMenu getRightClickPopup()
    1411 	{
    1412 		return popup;
    1413 	}
    1414
    1415 	/***
    1416 	 * Sets the right click popup menu.
    1417 	 * @param popup The popup
    1418 	 */
    1419 	public final void setRightClickPopup(JPopupMenu popup)
    1420 	{
    1421 		this.popup = popup;
    1422 	}
    1423
    1424 	/***
    1425 	 * Returns the `magic' caret position. This can be used to preserve
    1426 	 * the column position when moving up and down lines.
    1427 	 */
    1428 	public final int getMagicCaretPosition()
    1429 	{
    1430 		return magicCaret;
    1431 	}
    1432
    1433 	/***
    1434 	 * Sets the `magic' caret position. This can be used to preserve
    1435 	 * the column position when moving up and down lines.
    1436 	 * @param magicCaret The magic caret position
    1437 	 */
    1438 	public final void setMagicCaretPosition(int magicCaret)
    1439 	{
    1440 		this.magicCaret = magicCaret;
    1441 	}
    1442
    1443 	/***
    1444 	 * Similar to <code>setSelectedText()</code>, but overstrikes the
    1445 	 * appropriate number of characters if overwrite mode is enabled.
    1446 	 * @param str The string
    1447 	 * @see #setSelectedText(String)
    1448 	 * @see #isOverwriteEnabled()
    1449 	 */
    1450 	public void overwriteSetSelectedText(String str)
    1451 	{
    1452 		// Don't overstrike if there is a selection
    1453 		if(!overwrite || selectionStart != selectionEnd)
    1454 		{
    1455 			setSelectedText(str);
    1456 			return;
    1457 		}
    1458
    1459 		// Don't overstrike if we're on the end of
    1460 		// the line
    1461 		int caret = getCaretPosition();
    1462 		int caretLineEnd = getLineEndOffset(getCaretLine());
    1463 		if(caretLineEnd - caret <= str.length())
    1464 		{
    1465 			setSelectedText(str);
    1466 			return;
    1467 		}
    1468
    1469 		document.beginCompoundEdit();
    1470
    1471 		try
    1472 		{
    1473 			document.remove(caret,str.length());
    1474 			document.insertString(caret,str,null);
    1475 		}
    1476 		catch(BadLocationException bl)
    1477 		{
    1478 			SoapUI.logError( bl );
    1479 		}
    1480 		finally
    1481 		{
    1482 			document.endCompoundEdit();
    1483 		}
    1484 	}
    1485
    1486 	/***
    1487 	 * Returns true if overwrite mode is enabled, false otherwise.
    1488 	 */
    1489 	public final boolean isOverwriteEnabled()
    1490 	{
    1491 		return overwrite;
    1492 	}
    1493
    1494 	/***
    1495 	 * Sets if overwrite mode should be enabled.
    1496 	 * @param overwrite True if overwrite mode should be enabled,
    1497 	 * false otherwise.
    1498 	 */
    1499 	public final void setOverwriteEnabled(boolean overwrite)
    1500 	{
    1501 		this.overwrite = overwrite;
    1502 		painter.invalidateSelectedLines();
    1503 	}
    1504
    1505 	/***
    1506 	 * Returns true if the selection is rectangular, false otherwise.
    1507 	 */
    1508 	public final boolean isSelectionRectangular()
    1509 	{
    1510 		return rectSelect;
    1511 	}
    1512
    1513 	/***
    1514 	 * Sets if the selection should be rectangular.
    1515 	 * @param overwrite True if the selection should be rectangular,
    1516 	 * false otherwise.
    1517 	 */
    1518 	public final void setSelectionRectangular(boolean rectSelect)
    1519 	{
    1520 		this.rectSelect = rectSelect;
    1521 		painter.invalidateSelectedLines();
    1522 	}
    1523
    1524 	/***
    1525 	 * Returns the position of the highlighted bracket (the bracket
    1526 	 * matching the one before the caret)
    1527 	 */
    1528 	public final int getBracketPosition()
    1529 	{
    1530 		return bracketPosition;
    1531 	}
    1532
    1533 	/***
    1534 	 * Returns the line of the highlighted bracket (the bracket
    1535 	 * matching the one before the caret)
    1536 	 */
    1537 	public final int getBracketLine()
    1538 	{
    1539 		return bracketLine;
    1540 	}
    1541
    1542 	/***
    1543 	 * Adds a caret change listener to this text area.
    1544 	 * @param listener The listener
    1545 	 */
    1546 	public final void addCaretListener(CaretListener listener)
    1547 	{
    1548 		listenerList.add(CaretListener.class,listener);
    1549 	}
    1550
    1551 	/***
    1552 	 * Removes a caret change listener from this text area.
    1553 	 * @param listener The listener
    1554 	 */
    1555 	public final void removeCaretListener(CaretListener listener)
    1556 	{
    1557 		listenerList.remove(CaretListener.class,listener);
    1558 	}
    1559
    1560 	/***
    1561 	 * Deletes the selected text from the text area and places it
    1562 	 * into the clipboard.
    1563 	 */
    1564 	public void cut()
    1565 	{
    1566 		if(editable)
    1567 		{
    1568 			copy();
    1569 			setSelectedText("");
    1570 		}
    1571 	}
    1572
    1573 	/***
    1574 	 * Places the selected text into the clipboard.
    1575 	 */
    1576 	public void copy()
    1577 	{
    1578 		if(selectionStart != selectionEnd)
    1579 		{
    1580 			Clipboard clipboard = getToolkit().getSystemClipboard();
    1581
    1582 			String selection = getSelectedText();
    1583
    1584 			int repeatCount = inputHandler.getRepeatCount();
    1585 			StringBuffer buf = new StringBuffer();
    1586 			for(int i = 0; i < repeatCount; i++)
    1587 				buf.append(selection);
    1588
    1589 			clipboard.setContents(new StringSelection(buf.toString()),null);
    1590 		}
    1591 	}
    1592
    1593 	/***
    1594 	 * Inserts the clipboard contents into the text.
    1595 	 */
    1596 	public void paste()
    1597 	{
    1598 		if(editable)
    1599 		{
    1600 			Clipboard clipboard = getToolkit().getSystemClipboard();
    1601 			try
    1602 			{
    1603 				// The MacOS MRJ doesn't convert \r to \n,
    1604 				// so do it here
    1605 				String selection = ((String)clipboard
    1606 					.getContents(this).getTransferData(
    1607 					DataFlavor.stringFlavor))
    1608 					.replace('\r','\n');
    1609
    1610 				int repeatCount = inputHandler.getRepeatCount();
    1611 				StringBuffer buf = new StringBuffer();
    1612 				for(int i = 0; i < repeatCount; i++)
    1613 					buf.append(selection);
    1614 				selection = buf.toString();
    1615 				setSelectedText(selection);
    1616 			}
    1617 			catch(Exception e)
    1618 			{
    1619 				getToolkit().beep();
    1620 				System.err.println("Clipboard does not"
    1621 					+ " contain a string");
    1622 			}
    1623 		}
    1624 	}
    1625
    1626 	/***
    1627 	 * Called by the AWT when this component is removed from it's parent.
    1628 	 * This stops clears the currently focused component.
    1629 	 */
    1630 	public void removeNotify()
    1631 	{
    1632 		super.removeNotify();
    1633 		if(focusedComponentRef != null && focusedComponentRef.get() == this)
    1634 			focusedComponentRef = null;
    1635
    1636 		if( this.document != null )
    1637 			this.document.removeDocumentListener( documentHandler );
    1638 	}
    1639
    1640
    1641
    1642 	@Override
    1643 	public void addNotify()
    1644 	{
    1645 		super.addNotify();
    1646
    1647 		if( this.document != null )
    1648 			this.document.addDocumentListener( documentHandler );
    1649 	}
    1650
    1651 	/***
    1652 	 * Forwards key events directly to the input handler.
    1653 	 * This is slightly faster than using a KeyListener
    1654 	 * because some Swing overhead is avoided.
    1655 	 */
    1656 	public void processKeyEvent(KeyEvent evt)
    1657 	{
    1658 		if(inputHandler == null)
    1659 			return;
    1660 		switch(evt.getID())
    1661 		{
    1662 		case KeyEvent.KEY_TYPED:
    1663 			inputHandler.keyTyped(evt);
    1664 			break;
    1665 		case KeyEvent.KEY_PRESSED:
    1666 			inputHandler.keyPressed(evt);
    1667 			break;
    1668 		case KeyEvent.KEY_RELEASED:
    1669 			inputHandler.keyReleased(evt);
    1670 			break;
    1671 		}
    1672
    1673 		if( !evt.isConsumed() )
    1674 		{
    1675 			KeyListener[] keyListeners = getKeyListeners();
    1676 			for( KeyListener listener : keyListeners )
    1677 			{
    1678 				switch(evt.getID())
    1679 				{
    1680 				case KeyEvent.KEY_TYPED:
    1681 					listener.keyTyped(evt);
    1682 					break;
    1683 				case KeyEvent.KEY_PRESSED:
    1684 					listener.keyPressed(evt);
    1685 					break;
    1686 				case KeyEvent.KEY_RELEASED:
    1687 					listener.keyReleased(evt);
    1688 					break;
    1689 				}
    1690
    1691 				if( evt.isConsumed() )
    1692 					break;
    1693 			}
    1694
    1695 			if( !evt.isConsumed() )
    1696 				getParent().dispatchEvent( evt );
    1697 		}
    1698 	}
    1699
    1700 	// protected members
    1701 	protected static final String CENTER = "center";
    1702 	protected static final String RIGHT = "right";
    1703 	protected static final String BOTTOM = "bottom";
    1704
    1705 	protected static WeakReference<JEditTextArea> focusedComponentRef;
    1706 	protected static final Timer caretTimer;
    1707
    1708 	protected TextAreaPainter painter;
    1709
    1710 	protected JPopupMenu popup;
    1711
    1712 	protected EventListenerList listenerList;
    1713 	protected MutableCaretEvent caretEvent;
    1714
    1715 	protected boolean caretBlinks;
    1716 	protected boolean caretVisible;
    1717 	protected boolean blink;
    1718
    1719 	protected boolean editable;
    1720
    1721 	protected int firstLine;
    1722 	protected int visibleLines;
    1723 //	protected int electricScroll;
    1724
    1725 //	protected int horizontalOffset;
    1726
    1727 	//protected JScrollBar vertical;
    1728 	//protected JScrollBar horizontal;
    1729 	protected boolean scrollBarsInitialized;
    1730
    1731 	protected InputHandler inputHandler;
    1732 	protected SyntaxDocument document;
    1733 	protected DocumentHandler documentHandler;
    1734
    1735 	protected Segment lineSegment;
    1736
    1737 	protected int selectionStart;
    1738 	protected int selectionStartLine;
    1739 	protected int selectionEnd;
    1740 	protected int selectionEndLine;
    1741 	protected boolean biasLeft;
    1742
    1743 	protected int bracketPosition;
    1744 	protected int bracketLine;
    1745
    1746 	protected int magicCaret;
    1747 	protected boolean overwrite;
    1748 	protected boolean rectSelect;
    1749
    1750 	protected void fireCaretEvent()
    1751 	{
    1752 		Object[] listeners = listenerList.getListenerList();
    1753 		for(int i = listeners.length - 2; i >= 0; i--)
    1754 		{
    1755 			if(listeners[i] == CaretListener.class)
    1756 			{
    1757 				((CaretListener)listeners[i+1]).caretUpdate(caretEvent);
    1758 			}
    1759 		}
    1760 	}
    1761
    1762 	protected void updateBracketHighlight(int newCaretPosition)
    1763 	{
    1764 		if(newCaretPosition == 0)
    1765 		{
    1766 			bracketPosition = bracketLine = -1;
    1767 			return;
    1768 		}
    1769
    1770 		try
    1771 		{
    1772 			int offset = TextUtilities.findMatchingBracket(
    1773 				document,newCaretPosition - 1);
    1774 			if(offset != -1)
    1775 			{
    1776 				bracketLine = getLineOfOffset(offset);
    1777 				bracketPosition = offset - getLineStartOffset(bracketLine);
    1778 				return;
    1779 			}
    1780 		}
    1781 		catch(BadLocationException bl)
    1782 		{
    1783 			SoapUI.logError( bl );
    1784 		}
    1785
    1786 		bracketLine = bracketPosition = -1;
    1787 	}
    1788
    1789 	protected void documentChanged(DocumentEvent evt)
    1790 	{
    1791 		DocumentEvent.ElementChange ch = evt.getChange(
    1792 			document.getDefaultRootElement());
    1793
    1794 		int count;
    1795 		if(ch == null)
    1796 			count = 0;
    1797 		else
    1798 			count = ch.getChildrenAdded().length -
    1799 				ch.getChildrenRemoved().length;
    1800
    1801 		int line = getLineOfOffset(evt.getOffset());
    1802 		if(count == 0)
    1803 		{
    1804 			painter.invalidateLine(line);
    1805 		}
    1806 		// do magic stuff
    1807 		else if(line < firstLine)
    1808 		{
    1809 			setFirstLine(firstLine + count);
    1810 		}
    1811 		// end of magic stuff
    1812 		else
    1813 		{
    1814 			painter.invalidateLineRange(line,firstLine + visibleLines);
    1815 			updateScrollBars();
    1816 		}
    1817 	}
    1818
    1819 	class ScrollLayout implements LayoutManager
    1820 	{
    1821 		public void addLayoutComponent(String name, Component comp)
    1822 		{
    1823 			if(name.equals(CENTER))
    1824 				center = comp;
    1825 			/*
    1826 			else if(name.equals(RIGHT))
    1827 				right = comp;
    1828 			else if(name.equals(BOTTOM))
    1829 				bottom = comp;
    1830 			else if(name.equals(LEFT_OF_SCROLLBAR))
    1831 				leftOfScrollBar.addElement(comp);*/
    1832 		}
    1833
    1834 		public void removeLayoutComponent(Component comp)
    1835 		{
    1836 			if(center == comp)
    1837 				center = null;
    1838 			/*
    1839 			if(right == comp)
    1840 				right = null;
    1841 			if(bottom == comp)
    1842 				bottom = null;
    1843 			else
    1844 				leftOfScrollBar.removeElement(comp);*/
    1845 		}
    1846
    1847 		public Dimension preferredLayoutSize(Container parent)
    1848 		{
    1849 			Dimension dim = new Dimension();
    1850 			Insets insets = getInsets();
    1851 			dim.width = insets.left + insets.right;
    1852 			dim.height = insets.top + insets.bottom;
    1853
    1854 			Dimension centerPref = center.getPreferredSize();
    1855 			dim.width += centerPref.width;
    1856 			dim.height += centerPref.height;
    1857 			/*
    1858 			Dimension rightPref = right.getPreferredSize();
    1859 			dim.width += rightPref.width;
    1860 			Dimension bottomPref = bottom.getPreferredSize();
    1861 			dim.height += bottomPref.height;
    1862 		*/
    1863 			return dim;
    1864 		}
    1865
    1866 		public Dimension minimumLayoutSize(Container parent)
    1867 		{
    1868 			Dimension dim = new Dimension();
    1869 			Insets insets = getInsets();
    1870 			dim.width = insets.left + insets.right;
    1871 			dim.height = insets.top + insets.bottom;
    1872
    1873 			Dimension centerPref = center.getMinimumSize();
    1874 			dim.width += centerPref.width;
    1875 			dim.height += centerPref.height;
    1876 			/*
    1877 			Dimension rightPref = right.getMinimumSize();
    1878 			dim.width += rightPref.width;
    1879 			Dimension bottomPref = bottom.getMinimumSize();
    1880 			dim.height += bottomPref.height;
    1881 */
    1882 			return dim;
    1883 		}
    1884
    1885 		public void layoutContainer(Container parent)
    1886 		{
    1887 			Dimension size = parent.getSize();
    1888 			Insets insets = parent.getInsets();
    1889 			int itop = insets.top;
    1890 			int ileft = insets.left;
    1891 			int ibottom = insets.bottom;
    1892 			int iright = insets.right;
    1893
    1894 			//int rightWidth = right.getPreferredSize().width;
    1895 			//int bottomHeight = bottom.getPreferredSize().height;
    1896 			int centerWidth = size.width - ileft - iright;
    1897 			int centerHeight = size.height  - itop - ibottom;
    1898
    1899 			center.setBounds(
    1900 				ileft,
    1901 				itop,
    1902 				centerWidth,
    1903 				centerHeight);
    1904
    1905 /*			right.setBounds(
    1906 				ileft + centerWidth,
    1907 				itop,
    1908 				rightWidth,
    1909 				centerHeight);
    1910
    1911 			// Lay out all status components, in order
    1912 			Enumeration status = leftOfScrollBar.elements();
    1913 			while(status.hasMoreElements())
    1914 			{
    1915 				Component comp = (Component)status.nextElement();
    1916 				Dimension dim = comp.getPreferredSize();
    1917 				comp.setBounds(ileft,
    1918 					itop + centerHeight,
    1919 					dim.width,
    1920 					bottomHeight);
    1921 				ileft += dim.width;
    1922 			}
    1923
    1924 			bottom.setBounds(
    1925 				ileft,
    1926 				itop + centerHeight,
    1927 				size.width - rightWidth - ileft - iright,
    1928 				bottomHeight);
    1929 				*/
    1930 		}
    1931
    1932 		// private members
    1933 		private Component center;
    1934 		//private Component right;
    1935 		//private Component bottom;
    1936 		//private Vector<Component> leftOfScrollBar = new Vector<Component>();
    1937 	}
    1938
    1939 	static class CaretBlinker implements ActionListener
    1940 	{
    1941 		public void actionPerformed(ActionEvent evt)
    1942 		{
    1943 			if(focusedComponentRef != null && focusedComponentRef.get() != null
    1944 				&& focusedComponentRef.get().hasFocus())
    1945 				focusedComponentRef.get().blinkCaret();
    1946 		}
    1947 	}
    1948
    1949 	class MutableCaretEvent extends CaretEvent
    1950 	{
    1951 		MutableCaretEvent()
    1952 		{
    1953 			super(JEditTextArea.this);
    1954 		}
    1955
    1956 		public int getDot()
    1957 		{
    1958 			return getCaretPosition();
    1959 		}
    1960
    1961 		public int getMark()
    1962 		{
    1963 			return getMarkPosition();
    1964 		}
    1965 	}
    1966
    1967 	class AdjustHandler implements AdjustmentListener
    1968 	{
    1969 		public void adjustmentValueChanged(final AdjustmentEvent evt)
    1970 		{
    1971 			if(!scrollBarsInitialized)
    1972 				return;
    1973
    1974 			// If this is not done, mousePressed events accumilate
    1975 			// and the result is that scrolling doesn't stop after
    1976 			// the mouse is released
    1977 			SwingUtilities.invokeLater(new Runnable() {
    1978 				public void run()
    1979 				{
    1980 					/*
    1981 					if(evt.getAdjustable() == vertical)
    1982 						setFirstLine(vertical.getValue());
    1983 					else
    1984 						setHorizontalOffset(-horizontal.getValue());*/
    1985 				}
    1986 			});
    1987 		}
    1988 	}
    1989
    1990 	class ComponentHandler extends ComponentAdapter
    1991 	{
    1992 		public void componentResized(ComponentEvent evt)
    1993 		{
    1994 			recalculateVisibleLines();
    1995 			scrollBarsInitialized = true;
    1996 		}
    1997 	}
    1998
    1999 	class DocumentHandler implements DocumentListener
    2000 	{
    2001 		public void insertUpdate(DocumentEvent evt)
    2002 		{
    2003 			documentChanged(evt);
    2004
    2005 			int offset = evt.getOffset();
    2006 			int length = evt.getLength();
    2007
    2008 			int newStart;
    2009 			int newEnd;
    2010
    2011 			if(selectionStart > offset || (selectionStart
    2012 				== selectionEnd && selectionStart == offset))
    2013 				newStart = selectionStart + length;
    2014 			else
    2015 				newStart = selectionStart;
    2016
    2017 			if(selectionEnd >= offset)
    2018 				newEnd = selectionEnd + length;
    2019 			else
    2020 				newEnd = selectionEnd;
    2021
    2022 			select(newStart,newEnd);
    2023 		}
    2024
    2025 		public void removeUpdate(DocumentEvent evt)
    2026 		{
    2027 			documentChanged(evt);
    2028
    2029 			int offset = evt.getOffset();
    2030 			int length = evt.getLength();
    2031
    2032 			int newStart;
    2033 			int newEnd;
    2034
    2035 			if(selectionStart > offset)
    2036 			{
    2037 				if(selectionStart > offset + length)
    2038 					newStart = selectionStart - length;
    2039 				else
    2040 					newStart = offset;
    2041 			}
    2042 			else
    2043 				newStart = selectionStart;
    2044
    2045 			if(selectionEnd > offset)
    2046 			{
    2047 				if(selectionEnd > offset + length)
    2048 					newEnd = selectionEnd - length;
    2049 				else
    2050 					newEnd = offset;
    2051 			}
    2052 			else
    2053 				newEnd = selectionEnd;
    2054
    2055 			select(newStart,newEnd);
    2056 		}
    2057
    2058 		public void changedUpdate(DocumentEvent evt)
    2059 		{
    2060 		}
    2061 	}
    2062
    2063 	class DragHandler implements MouseMotionListener
    2064 	{
    2065 		public void mouseDragged(MouseEvent evt)
    2066 		{
    2067 			if(popup != null && popup.isVisible())
    2068 				return;
    2069
    2070 			setSelectionRectangular((evt.getModifiers()
    2071 				& InputEvent.CTRL_MASK) != 0);
    2072 			select(getMarkPosition(),xyToOffset(evt.getX(),evt.getY()));
    2073 		}
    2074
    2075 		public void mouseMoved(MouseEvent evt) {}
    2076 	}
    2077
    2078 	class FocusHandler implements FocusListener
    2079 	{
    2080 		public void focusGained(FocusEvent evt)
    2081 		{
    2082 			if( isEditable() )
    2083 				setCaretVisible(true);
    2084 			focusedComponentRef = new WeakReference<JEditTextArea>( JEditTextArea.this );
    2085 		}
    2086
    2087 		public void focusLost(FocusEvent evt)
    2088 		{
    2089 			setCaretVisible(false);
    2090 			focusedComponentRef = null;
    2091 		}
    2092 	}
    2093
    2094 	class MouseHandler extends MouseAdapter
    2095 	{
    2096 		@Override
    2097 		public void mouseClicked( MouseEvent e )
    2098 		{
    2099 			if( popup != null && e.isPopupTrigger() )
    2100 			{
    2101 				doPopup(e);
    2102 			}
    2103 		}
    2104
    2105 		private void doPopup(MouseEvent evt)
    2106 		{
    2107 			popup.show(painter,evt.getX(),evt.getY());
    2108 		}
    2109
    2110 		@Override
    2111 		public void mouseReleased( MouseEvent e )
    2112 		{
    2113 			if( popup != null && e.isPopupTrigger() )
    2114 			{
    2115 				doPopup(e);
    2116 			}
    2117 		}
    2118
    2119 		public void mousePressed(MouseEvent evt)
    2120 		{
    2121 			requestFocus();
    2122
    2123 			// Focus events not fired sometimes?
    2124 			if( isEditable() )
    2125 				setCaretVisible(true);
    2126
    2127 			focusedComponentRef = new WeakReference<JEditTextArea>( JEditTextArea.this );
    2128
    2129 			if( popup != null && evt.isPopupTrigger() )
    2130 			{
    2131 				doPopup( evt );
    2132 				return;
    2133 			}
    2134
    2135 			if( evt.getButton() != MouseEvent.BUTTON1 )
    2136 			{
    2137 				return;
    2138 			}
    2139
    2140 			int line = yToLine(evt.getY());
    2141 			int offset = xToOffset(line,evt.getX());
    2142 			int dot = getLineStartOffset(line) + offset;
    2143
    2144 			switch(evt.getClickCount())
    2145 			{
    2146 			case 1:
    2147 				doSingleClick(evt,line,offset,dot);
    2148 				break;
    2149 			case 2:
    2150 				// It uses the bracket matching stuff, so
    2151 				// it can throw a BLE
    2152 				try
    2153 				{
    2154 					doDoubleClick(evt,line,offset,dot);
    2155 				}
    2156 				catch(BadLocationException bl)
    2157 				{
    2158 					SoapUI.logError( bl );
    2159 				}
    2160 				break;
    2161 			case 3:
    2162 				doTripleClick(evt,line,offset,dot);
    2163 				break;
    2164 			}
    2165 		}
    2166
    2167 		private void doSingleClick(MouseEvent evt, int line,
    2168 			int offset, int dot)
    2169 		{
    2170 			if((evt.getModifiers() & InputEvent.SHIFT_MASK) != 0)
    2171 			{
    2172 				rectSelect = (evt.getModifiers() & InputEvent.CTRL_MASK) != 0;
    2173 				select(getMarkPosition(),dot);
    2174 			}
    2175 			else
    2176 				setCaretPosition(dot);
    2177 		}
    2178
    2179 		private void doDoubleClick(MouseEvent evt, int line,
    2180 			int offset, int dot) throws BadLocationException
    2181 		{
    2182 			// Ignore empty lines
    2183 			if(getTabExpandedLineLength(line) == 0)
    2184 				return;
    2185
    2186 			try
    2187 			{
    2188 				int bracket = TextUtilities.findMatchingBracket(
    2189 					document,Math.max(0,dot - 1));
    2190 				if(bracket != -1)
    2191 				{
    2192 					int mark = getMarkPosition();
    2193 					// Hack
    2194 					if(bracket > mark)
    2195 					{
    2196 						bracket++;
    2197 						mark--;
    2198 					}
    2199 					select(mark,bracket);
    2200 					return;
    2201 				}
    2202 			}
    2203 			catch(BadLocationException bl)
    2204 			{
    2205 				SoapUI.logError( bl );
    2206 			}
    2207
    2208 			// Ok, it's not a bracket... select the word
    2209 			String lineText = getLineText(line);
    2210 			char ch = lineText.charAt(Math.max(0,offset - 1));
    2211
    2212 			String noWordSep = (String)document.getProperty("noWordSep");
    2213 			if(noWordSep == null)
    2214 				noWordSep = "";
    2215
    2216 			// If the user clicked on a non-letter char,
    2217 			// we select the surrounding non-letters
    2218 			boolean selectNoLetter = (!Character
    2219 				.isLetterOrDigit(ch)
    2220 				&& noWordSep.indexOf(ch) == -1);
    2221
    2222 			int wordStart = 0;
    2223
    2224 			for(int i = offset - 1; i >= 0; i--)
    2225 			{
    2226 				ch = lineText.charAt(i);
    2227 				if(selectNoLetter ^ (!Character
    2228 					.isLetterOrDigit(ch) &&
    2229 					noWordSep.indexOf(ch) == -1))
    2230 				{
    2231 					wordStart = i + 1;
    2232 					break;
    2233 				}
    2234 			}
    2235
    2236 			int wordEnd = lineText.length();
    2237 			for(int i = offset; i < lineText.length(); i++)
    2238 			{
    2239 				ch = lineText.charAt(i);
    2240 				if(selectNoLetter ^ (!Character
    2241 					.isLetterOrDigit(ch) &&
    2242 					noWordSep.indexOf(ch) == -1))
    2243 				{
    2244 					wordEnd = i;
    2245 					break;
    2246 				}
    2247 			}
    2248
    2249 			int lineStart = getLineStartOffset(line);
    2250 			select(lineStart + wordStart,lineStart + wordEnd);
    2251
    2252 			/*
    2253 			String lineText = getLineText(line);
    2254 			String noWordSep = (String)document.getProperty("noWordSep");
    2255 			int wordStart = TextUtilities.findWordStart(lineText,offset,noWordSep);
    2256 			int wordEnd = TextUtilities.findWordEnd(lineText,offset,noWordSep);
    2257
    2258 			int lineStart = getLineStartOffset(line);
    2259 			select(lineStart + wordStart,lineStart + wordEnd);
    2260 			*/
    2261 		}
    2262
    2263 		private void doTripleClick(MouseEvent evt, int line,
    2264 			int offset, int dot)
    2265 		{
    2266 			select(getLineStartOffset(line),getLineEndOffset(line)-1);
    2267 		}
    2268 	}
    2269
    2270 	class CaretUndo extends AbstractUndoableEdit
    2271 	{
    2272 		private int start;
    2273 		private int end;
    2274
    2275 		CaretUndo(int start, int end)
    2276 		{
    2277 			this.start = start;
    2278 			this.end = end;
    2279 		}
    2280
    2281 		public boolean isSignificant()
    2282 		{
    2283 			return false;
    2284 		}
    2285
    2286 		public String getPresentationName()
    2287 		{
    2288 			return "caret move";
    2289 		}
    2290
    2291 		public void undo() throws CannotUndoException
    2292 		{
    2293 			super.undo();
    2294
    2295 			select(start,end);
    2296 		}
    2297
    2298 		public void redo() throws CannotRedoException
    2299 		{
    2300 			super.redo();
    2301
    2302 			select(start,end);
    2303 		}
    2304
    2305 		public boolean addEdit(UndoableEdit edit)
    2306 		{
    2307 			if(edit instanceof CaretUndo)
    2308 			{
    2309 				CaretUndo cedit = (CaretUndo)edit;
    2310 				start = cedit.start;
    2311 				end = cedit.end;
    2312 				cedit.die();
    2313
    2314 				return true;
    2315 			}
    2316 			else
    2317 				return false;
    2318 		}
    2319 	}
    2320
    2321 	static
    2322 	{
    2323 		caretTimer = new Timer(500,new CaretBlinker());
    2324 		caretTimer.setInitialDelay(500);
    2325 		caretTimer.start();
    2326 	}
    2327
    2328 	public Dimension getPreferredSize()
    2329 	{
    2330 		Dimension preferredSize = painter.getPreferredSize();
    2331
    2332 		if( getParent() instanceof JViewport )
    2333 		{
    2334 			JViewport viewport = ( JViewport ) getParent();
    2335 			Dimension size = viewport.getSize();
    2336
    2337 			preferredSize = new Dimension(
    2338 						(int)(preferredSize.getWidth() < size.getWidth() ? size.getWidth() : preferredSize.getWidth()),
    2339 						(int)(preferredSize.getHeight() < size.getHeight() ? size.getHeight() : preferredSize.getHeight()) );
    2340 		}
    2341
    2342 		return preferredSize;
    2343 	}
    2344
    2345 	public Dimension getMaximumSize()
    2346 	{
    2347 		return painter.getMaximumSize();
    2348 	}
    2349
    2350 	public Dimension getMinimumSize()
    2351 	{
    2352 		return painter.getMinimumSize();
    2353 	}
    2354
    2355 	public int getMaxLineLength()
    2356 	{
    2357 		int max = 0;
    2358
    2359 		for( int c = 0; c < getLineCount(); c++ )
    2360 		{
    2361 			int lineLength = getTabExpandedLineLength( c );
    2362 			if( lineLength > max )
    2363 				max = lineLength;
    2364 		}
    2365
    2366 		return max;
    2367 	}
    2368
    2369 	public Dimension getPreferredScrollableViewportSize()
    2370 	{
    2371 		return getPreferredSize();
    2372 	}
    2373
    2374 	public int getScrollableBlockIncrement( Rectangle arg0, int arg1, int arg2 )
    2375 	{
    2376 		return getFontMetrics( getFont() ).getHeight()*5;
    2377 	}
    2378
    2379 	public boolean getScrollableTracksViewportHeight()
    2380 	{
    2381 		return false;
    2382 	}
    2383
    2384 	public boolean getScrollableTracksViewportWidth()
    2385 	{
    2386 		return false;
    2387 	}
    2388
    2389 	public int getScrollableUnitIncrement( Rectangle arg0, int arg1, int arg2 )
    2390 	{
    2391 		return getFontMetrics( getFont() ).getHeight();
    2392 	}
    2393 }