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(getLineLength(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()
    514 			- (fm.getLeading() + fm.getMaxDescent());
    515 	}
    516
    517 	/***
    518 	 * Converts a y co-ordinate to a line index.
    519 	 * @param y The y co-ordinate
    520 	 */
    521 	public int yToLine(int y)
    522 	{
    523 		FontMetrics fm = painter.getFontMetrics();
    524 		int height = fm.getHeight();
    525 		return Math.max(0,Math.min(getLineCount() - 1,
    526 			y / height + firstLine));
    527 	}
    528
    529 	/***
    530 	 * Converts an offset in a line into an x co-ordinate. This is a
    531 	 * slow version that can be used any time.
    532 	 * @param line The line
    533 	 * @param offset The offset, from the start of the line
    534 	 */
    535 	public final int offsetToX(int line, int offset)
    536 	{
    537 		// don't use cached tokens
    538 		painter.currentLineTokens = null;
    539 		return _offsetToX(line,offset);
    540 	}
    541
    542 	/***
    543 	 * Converts an offset in a line into an x co-ordinate. This is a
    544 	 * fast version that should only be used if no changes were made
    545 	 * to the text since the last repaint.
    546 	 * @param line The line
    547 	 * @param offset The offset, from the start of the line
    548 	 */
    549 	public int _offsetToX(int line, int offset)
    550 	{
    551 		TokenMarker tokenMarker = getTokenMarker();
    552
    553 		/* Use painter's cached info for speed */
    554 		FontMetrics fm = painter.getFontMetrics();
    555
    556 		getLineText(line,lineSegment);
    557
    558 		int segmentOffset = lineSegment.offset;
    559 		int x = 0; //-horizontalOffset;
    560
    561 		/* If syntax coloring is disabled, do simple translation */
    562 		if(tokenMarker == null)
    563 		{
    564 			lineSegment.count = offset;
    565 			return x + Utilities.getTabbedTextWidth(lineSegment,
    566 				fm,x,painter,0);
    567 		}
    568 		/* If syntax coloring is enabled, we have to do this because
    569 		 * tokens can vary in width */
    570 		else
    571 		{
    572 			Token tokens;
    573 			if(painter.currentLineIndex == line
    574 				&& painter.currentLineTokens != null)
    575 				tokens = painter.currentLineTokens;
    576 			else
    577 			{
    578 				painter.currentLineIndex = line;
    579 				tokens = painter.currentLineTokens
    580 					= tokenMarker.markTokens(lineSegment,line);
    581 			}
    582
    583 //			Toolkit toolkit = painter.getToolkit();
    584 			Font defaultFont = painter.getFont();
    585 			SyntaxStyle[] styles = painter.getStyles();
    586
    587 			for(;;)
    588 			{
    589 				byte id = tokens.id;
    590 				if(id == Token.END)
    591 				{
    592 					return x;
    593 				}
    594
    595 				if(id == Token.NULL)
    596 					fm = painter.getFontMetrics();
    597 				else
    598 					fm = styles[id].getFontMetrics(defaultFont);
    599
    600 				int length = tokens.length;
    601
    602 				if(offset + segmentOffset < lineSegment.offset + length)
    603 				{
    604 					lineSegment.count = offset - (lineSegment.offset - segmentOffset);
    605 					return x + Utilities.getTabbedTextWidth(
    606 						lineSegment,fm,x,painter,0);
    607 				}
    608 				else
    609 				{
    610 					lineSegment.count = length;
    611 					x += Utilities.getTabbedTextWidth(
    612 						lineSegment,fm,x,painter,0);
    613 					lineSegment.offset += length;
    614 				}
    615 				tokens = tokens.next;
    616 			}
    617 		}
    618 	}
    619
    620 	/***
    621 	 * Converts an x co-ordinate to an offset within a line.
    622 	 * @param line The line
    623 	 * @param x The x co-ordinate
    624 	 */
    625 	public int xToOffset(int line, int x)
    626 	{
    627 		TokenMarker tokenMarker = getTokenMarker();
    628
    629 		/* Use painter's cached info for speed */
    630 		FontMetrics fm = painter.getFontMetrics();
    631
    632 		getLineText(line,lineSegment);
    633
    634 		char[] segmentArray = lineSegment.array;
    635 		int segmentOffset = lineSegment.offset;
    636 		int segmentCount = lineSegment.count;
    637
    638 		int width = 0; //-horizontalOffset;
    639
    640 		if(tokenMarker == null)
    641 		{
    642 			for(int i = 0; i < segmentCount; i++)
    643 			{
    644 				char c = segmentArray[i + segmentOffset];
    645 				int charWidth;
    646 				if(c == '\t')
    647 					charWidth = (int)painter.nextTabStop(width,i)
    648 						- width;
    649 				else
    650 					charWidth = fm.charWidth(c);
    651
    652 				if(painter.isBlockCaretEnabled())
    653 				{
    654 					if(x - charWidth <= width)
    655 						return i;
    656 				}
    657 				else
    658 				{
    659 					if(x - charWidth / 2 <= width)
    660 						return i;
    661 				}
    662
    663 				width += charWidth;
    664 			}
    665
    666 			return segmentCount;
    667 		}
    668 		else
    669 		{
    670 			Token tokens;
    671 			if(painter.currentLineIndex == line && painter
    672 				.currentLineTokens != null)
    673 				tokens = painter.currentLineTokens;
    674 			else
    675 			{
    676 				painter.currentLineIndex = line;
    677 				tokens = painter.currentLineTokens
    678 					= tokenMarker.markTokens(lineSegment,line);
    679 			}
    680
    681 			int offset = 0;
    682 //			Toolkit toolkit = painter.getToolkit();
    683 			Font defaultFont = painter.getFont();
    684 			SyntaxStyle[] styles = painter.getStyles();
    685
    686 			for(;;)
    687 			{
    688 				byte id = tokens.id;
    689 				if(id == Token.END)
    690 					return offset;
    691
    692 				if(id == Token.NULL)
    693 					fm = painter.getFontMetrics();
    694 				else
    695 					fm = styles[id].getFontMetrics(defaultFont);
    696
    697 				int length = tokens.length;
    698
    699 				for(int i = 0; i < length; i++)
    700 				{
    701 					char c = segmentArray[segmentOffset + offset + i];
    702 					int charWidth;
    703 					if(c == '\t')
    704 						charWidth = (int)painter.nextTabStop(width,offset + i)
    705 							- width;
    706 					else
    707 						charWidth = fm.charWidth(c);
    708
    709 					if(painter.isBlockCaretEnabled())
    710 					{
    711 						if(x - charWidth <= width)
    712 							return offset + i;
    713 					}
    714 					else
    715 					{
    716 						if(x - charWidth / 2 <= width)
    717 							return offset + i;
    718 					}
    719
    720 					width += charWidth;
    721 				}
    722
    723 				offset += length;
    724 				tokens = tokens.next;
    725 			}
    726 		}
    727 	}
    728
    729 	/***
    730 	 * Converts a point to an offset, from the start of the text.
    731 	 * @param x The x co-ordinate of the point
    732 	 * @param y The y co-ordinate of the point
    733 	 */
    734 	public int xyToOffset(int x, int y)
    735 	{
    736 		int line = yToLine(y);
    737 		int start = getLineStartOffset(line);
    738 		return start + xToOffset(line,x);
    739 	}
    740
    741 	/***
    742 	 * Returns the document this text area is editing.
    743 	 */
    744 	public final SyntaxDocument getDocument()
    745 	{
    746 		return document;
    747 	}
    748
    749 	/***
    750 	 * Sets the document this text area is editing.
    751 	 * @param document The document
    752 	 */
    753 	public void setDocument(SyntaxDocument document)
    754 	{
    755 		if(this.document == document)
    756 			return;
    757 		if(this.document != null)
    758 			this.document.removeDocumentListener(documentHandler);
    759 		this.document = document;
    760
    761 		if( getParent() != null )
    762 			document.addDocumentListener(documentHandler);
    763
    764 		select(0,0);
    765 		updateScrollBars();
    766 		painter.repaint();
    767 	}
    768
    769 	/***
    770 	 * Returns the document's token marker. Equivalent to calling
    771 	 * <code>getDocument().getTokenMarker()</code>.
    772 	 */
    773 	public final TokenMarker getTokenMarker()
    774 	{
    775 		return document.getTokenMarker();
    776 	}
    777
    778 	/***
    779 	 * Sets the document's token marker. Equivalent to caling
    780 	 * <code>getDocument().setTokenMarker()</code>.
    781 	 * @param tokenMarker The token marker
    782 	 */
    783 	public final void setTokenMarker(TokenMarker tokenMarker)
    784 	{
    785 		document.setTokenMarker(tokenMarker);
    786 	}
    787
    788 	/***
    789 	 * Returns the length of the document. Equivalent to calling
    790 	 * <code>getDocument().getLength()</code>.
    791 	 */
    792 	public final int getDocumentLength()
    793 	{
    794 		return document.getLength();
    795 	}
    796
    797 	/***
    798 	 * Returns the number of lines in the document.
    799 	 */
    800 	public final int getLineCount()
    801 	{
    802 		return document.getDefaultRootElement().getElementCount();
    803 	}
    804
    805 	/***
    806 	 * Returns the line containing the specified offset.
    807 	 * @param offset The offset
    808 	 */
    809 	public final int getLineOfOffset(int offset)
    810 	{
    811 		return document.getDefaultRootElement().getElementIndex(offset);
    812 	}
    813
    814 	/***
    815 	 * Returns the start offset of the specified line.
    816 	 * @param line The line
    817 	 * @return The start offset of the specified line, or -1 if the line is
    818 	 * invalid
    819 	 */
    820 	public int getLineStartOffset(int line)
    821 	{
    822 		Element lineElement = document.getDefaultRootElement()
    823 			.getElement(line);
    824 		if(lineElement == null)
    825 			return -1;
    826 		else
    827 			return lineElement.getStartOffset();
    828 	}
    829
    830 	/***
    831 	 * Returns the end offset of the specified line.
    832 	 * @param line The line
    833 	 * @return The end offset of the specified line, or -1 if the line is
    834 	 * invalid.
    835 	 */
    836 	public int getLineEndOffset(int line)
    837 	{
    838 		Element lineElement = document.getDefaultRootElement()
    839 			.getElement(line);
    840 		if(lineElement == null)
    841 			return -1;
    842 		else
    843 			return lineElement.getEndOffset();
    844 	}
    845
    846 	/***
    847 	 * Returns the length of the specified line.
    848 	 * @param line The line
    849 	 */
    850 	public int getLineLength(int line)
    851 	{
    852 		Element lineElement = document.getDefaultRootElement()
    853 			.getElement(line);
    854 		if(lineElement == null)
    855 			return -1;
    856 		else
    857 			return lineElement.getEndOffset()
    858 				- lineElement.getStartOffset() - 1;
    859 	}
    860
    861 	/***
    862 	 * Returns the entire text of this text area.
    863 	 */
    864 	public String getText()
    865 	{
    866 		try
    867 		{
    868 			return document.getText(0,document.getLength());
    869 		}
    870 		catch(BadLocationException bl)
    871 		{
    872 			SoapUI.logError( bl );
    873 			return null;
    874 		}
    875 	}
    876
    877 	/***
    878 	 * Sets the entire text of this text area.
    879 	 */
    880 	public synchronized void setText(String text)
    881 	{
    882 		try
    883 		{
    884 			document.beginCompoundEdit();
    885 			document.remove(0,document.getLength());
    886 			document.insertString(0,text,null);
    887
    888 			revalidate();
    889 		}
    890 		catch(BadLocationException bl)
    891 		{
    892 			SoapUI.logError( bl );
    893 		}
    894 		finally
    895 		{
    896 			document.endCompoundEdit();
    897 		}
    898 	}
    899
    900 	/***
    901 	 * Returns the specified substring of the document.
    902 	 * @param start The start offset
    903 	 * @param len The length of the substring
    904 	 * @return The substring, or null if the offsets are invalid
    905 	 */
    906 	public final String getText(int start, int len)
    907 	{
    908 		try
    909 		{
    910 			return document.getText(start,len);
    911 		}
    912 		catch(BadLocationException bl)
    913 		{
    914 			SoapUI.logError( bl );
    915 			return null;
    916 		}
    917 	}
    918
    919 	/***
    920 	 * Copies the specified substring of the document into a segment.
    921 	 * If the offsets are invalid, the segment will contain a null string.
    922 	 * @param start The start offset
    923 	 * @param len The length of the substring
    924 	 * @param segment The segment
    925 	 */
    926 	public final void getText(int start, int len, Segment segment)
    927 	{
    928 		try
    929 		{
    930 			document.getText(start,len,segment);
    931 		}
    932 		catch(BadLocationException bl)
    933 		{
    934 			SoapUI.logError( bl );
    935 			segment.offset = segment.count = 0;
    936 		}
    937 	}
    938
    939 	/***
    940 	 * Returns the text on the specified line.
    941 	 * @param lineIndex The line
    942 	 * @return The text, or null if the line is invalid
    943 	 */
    944 	public final String getLineText(int lineIndex)
    945 	{
    946 		int start = getLineStartOffset(lineIndex);
    947 		return getText(start,getLineEndOffset(lineIndex) - start - 1);
    948 	}
    949
    950 	/***
    951 	 * Copies the text on the specified line into a segment. If the line
    952 	 * is invalid, the segment will contain a null string.
    953 	 * @param lineIndex The line
    954 	 */
    955 	public final void getLineText(int lineIndex, Segment segment)
    956 	{
    957 		int start = getLineStartOffset(lineIndex);
    958 		getText(start,getLineEndOffset(lineIndex) - start - 1,segment);
    959 	}
    960
    961 	/***
    962 	 * Returns the selection start offset.
    963 	 */
    964 	public final int getSelectionStart()
    965 	{
    966 		return selectionStart;
    967 	}
    968
    969 	/***
    970 	 * Returns the offset where the selection starts on the specified
    971 	 * line.
    972 	 */
    973 	public int getSelectionStart(int line)
    974 	{
    975 		if(line == selectionStartLine)
    976 			return selectionStart;
    977 		else if(rectSelect)
    978 		{
    979 			Element map = document.getDefaultRootElement();
    980 			int start = selectionStart - map.getElement(selectionStartLine)
    981 				.getStartOffset();
    982
    983 			Element lineElement = map.getElement(line);
    984 			int lineStart = lineElement.getStartOffset();
    985 			int lineEnd = lineElement.getEndOffset() - 1;
    986 			return Math.min(lineEnd,lineStart + start);
    987 		}
    988 		else
    989 			return getLineStartOffset(line);
    990 	}
    991
    992 	/***
    993 	 * Returns the selection start line.
    994 	 */
    995 	public final int getSelectionStartLine()
    996 	{
    997 		return selectionStartLine;
    998 	}
    999
    1000 	/***
    1001 	 * Sets the selection start. The new selection will be the new
    1002 	 * selection start and the old selection end.
    1003 	 * @param selectionStart The selection start
    1004 	 * @see #select(int,int)
    1005 	 */
    1006 	public final void setSelectionStart(int selectionStart)
    1007 	{
    1008 		select(selectionStart,selectionEnd);
    1009 	}
    1010
    1011 	/***
    1012 	 * Returns the selection end offset.
    1013 	 */
    1014 	public final int getSelectionEnd()
    1015 	{
    1016 		return selectionEnd;
    1017 	}
    1018
    1019 	/***
    1020 	 * Returns the offset where the selection ends on the specified
    1021 	 * line.
    1022 	 */
    1023 	public int getSelectionEnd(int line)
    1024 	{
    1025 		if(line == selectionEndLine)
    1026 			return selectionEnd;
    1027 		else if(rectSelect)
    1028 		{
    1029 			Element map = document.getDefaultRootElement();
    1030 			int end = selectionEnd - map.getElement(selectionEndLine)
    1031 				.getStartOffset();
    1032
    1033 			Element lineElement = map.getElement(line);
    1034 			int lineStart = lineElement.getStartOffset();
    1035 			int lineEnd = lineElement.getEndOffset() - 1;
    1036 			return Math.min(lineEnd,lineStart + end);
    1037 		}
    1038 		else
    1039 			return getLineEndOffset(line) - 1;
    1040 	}
    1041
    1042 	/***
    1043 	 * Returns the selection end line.
    1044 	 */
    1045 	public final int getSelectionEndLine()
    1046 	{
    1047 		return selectionEndLine;
    1048 	}
    1049
    1050 	/***
    1051 	 * Sets the selection end. The new selection will be the old
    1052 	 * selection start and the bew selection end.
    1053 	 * @param selectionEnd The selection end
    1054 	 * @see #select(int,int)
    1055 	 */
    1056 	public final void setSelectionEnd(int selectionEnd)
    1057 	{
    1058 		select(selectionStart,selectionEnd);
    1059 	}
    1060
    1061 	/***
    1062 	 * Returns the caret position. This will either be the selection
    1063 	 * start or the selection end, depending on which direction the
    1064 	 * selection was made in.
    1065 	 */
    1066 	public final int getCaretPosition()
    1067 	{
    1068 		return (biasLeft ? selectionStart : selectionEnd);
    1069 	}
    1070
    1071 	/***
    1072 	 * Returns the caret line.
    1073 	 */
    1074 	public final int getCaretLine()
    1075 	{
    1076 		return (biasLeft ? selectionStartLine : selectionEndLine);
    1077 	}
    1078
    1079 	/***
    1080 	 * Returns the mark position. This will be the opposite selection
    1081 	 * bound to the caret position.
    1082 	 * @see #getCaretPosition()
    1083 	 */
    1084 	public final int getMarkPosition()
    1085 	{
    1086 		return (biasLeft ? selectionEnd : selectionStart);
    1087 	}
    1088
    1089 	/***
    1090 	 * Returns the mark line.
    1091 	 */
    1092 	public final int getMarkLine()
    1093 	{
    1094 		return (biasLeft ? selectionEndLine : selectionStartLine);
    1095 	}
    1096
    1097 	/***
    1098 	 * Sets the caret position. The new selection will consist of the
    1099 	 * caret position only (hence no text will be selected)
    1100 	 * @param caret The caret position
    1101 	 * @see #select(int,int)
    1102 	 */
    1103 	public final void setCaretPosition(int caret)
    1104 	{
    1105 		select(caret,caret);
    1106 	}
    1107
    1108 	/***
    1109 	 * Selects all text in the document.
    1110 	 */
    1111 	public final void selectAll()
    1112 	{
    1113 		select(0,getDocumentLength());
    1114 	}
    1115
    1116 	/***
    1117 	 * Moves the mark to the caret position.
    1118 	 */
    1119 	public final void selectNone()
    1120 	{
    1121 		select(getCaretPosition(),getCaretPosition());
    1122 	}
    1123
    1124 	/***
    1125 	 * Selects from the start offset to the end offset. This is the
    1126 	 * general selection method used by all other selecting methods.
    1127 	 * The caret position will be start if start &lt; end, and end
    1128 	 * if end &gt; start.
    1129 	 * @param start The start offset
    1130 	 * @param end The end offset
    1131 	 */
    1132 	public void select(int start, int end)
    1133 	{
    1134 		int newStart, newEnd;
    1135 		boolean newBias;
    1136 		if(start <= end)
    1137 		{
    1138 			newStart = start;
    1139 			newEnd = end;
    1140 			newBias = false;
    1141 		}
    1142 		else
    1143 		{
    1144 			newStart = end;
    1145 			newEnd = start;
    1146 			newBias = true;
    1147 		}
    1148
    1149 		if(newStart < 0 || newEnd > getDocumentLength())
    1150 		{
    1151 			throw new IllegalArgumentException("Bounds out of"
    1152 				+ " range: " + newStart + "," +
    1153 				newEnd);
    1154 		}
    1155
    1156 		// If the new position is the same as the old, we don't
    1157 		// do all this crap, however we still do the stuff at
    1158 		// the end (clearing magic position, scrolling)
    1159 		if(newStart != selectionStart || newEnd != selectionEnd
    1160 			|| newBias != biasLeft)
    1161 		{
    1162 			int newStartLine = getLineOfOffset(newStart);
    1163 			int newEndLine = getLineOfOffset(newEnd);
    1164
    1165 			if(painter.isBracketHighlightEnabled())
    1166 			{
    1167 				if(bracketLine != -1)
    1168 					painter.invalidateLine(bracketLine);
    1169 				updateBracketHighlight(end);
    1170 				if(bracketLine != -1)
    1171 					painter.invalidateLine(bracketLine);
    1172 			}
    1173
    1174 			painter.invalidateLineRange(selectionStartLine,selectionEndLine);
    1175 			painter.invalidateLineRange(newStartLine,newEndLine);
    1176
    1177 			document.addUndoableEdit(new CaretUndo(
    1178 				selectionStart,selectionEnd));
    1179
    1180 			selectionStart = newStart;
    1181 			selectionEnd = newEnd;
    1182 			selectionStartLine = newStartLine;
    1183 			selectionEndLine = newEndLine;
    1184 			biasLeft = newBias;
    1185
    1186 			fireCaretEvent();
    1187 		}
    1188
    1189 		// When the user is typing, etc, we don't want the caret
    1190 		// to blink
    1191 		blink = true;
    1192 		caretTimer.restart();
    1193
    1194 		// Disable rectangle select if selection start = selection end
    1195 		if(selectionStart == selectionEnd)
    1196 			rectSelect = false;
    1197
    1198 		// Clear the `magic' caret position used by up/down
    1199 		magicCaret = -1;
    1200
    1201 		scrollToCaret();
    1202 	}
    1203
    1204 	/***
    1205 	 * Returns the selected text, or null if no selection is active.
    1206 	 */
    1207 	public final String getSelectedText()
    1208 	{
    1209 		if(selectionStart == selectionEnd)
    1210 			return null;
    1211
    1212 		if(rectSelect)
    1213 		{
    1214 			// Return each row of the selection on a new line
    1215
    1216 			Element map = document.getDefaultRootElement();
    1217
    1218 			int start = selectionStart - map.getElement(selectionStartLine)
    1219 				.getStartOffset();
    1220 			int end = selectionEnd - map.getElement(selectionEndLine)
    1221 				.getStartOffset();
    1222
    1223 			// Certain rectangles satisfy this condition...
    1224 			if(end < start)
    1225 			{
    1226 				int tmp = end;
    1227 				end = start;
    1228 				start = tmp;
    1229 			}
    1230
    1231 			StringBuffer buf = new StringBuffer();
    1232 			Segment seg = new Segment();
    1233
    1234 			for(int i = selectionStartLine; i <= selectionEndLine; i++)
    1235 			{
    1236 				Element lineElement = map.getElement(i);
    1237 				int lineStart = lineElement.getStartOffset();
    1238 				int lineEnd = lineElement.getEndOffset() - 1;
    1239 				int lineLen = lineEnd - lineStart;
    1240
    1241 				lineStart = Math.min(lineStart + start,lineEnd);
    1242 				lineLen = Math.min(end - start,lineEnd - lineStart);
    1243
    1244 				getText(lineStart,lineLen,seg);
    1245 				buf.append(seg.array,seg.offset,seg.count);
    1246
    1247 				if(i != selectionEndLine)
    1248 					buf.append('\n');
    1249 			}
    1250
    1251 			return buf.toString();
    1252 		}
    1253 		else
    1254 		{
    1255 			return getText(selectionStart,
    1256 				selectionEnd - selectionStart);
    1257 		}
    1258 	}
    1259
    1260 	/***
    1261 	 * Replaces the selection with the specified text.
    1262 	 * @param selectedText The replacement text for the selection
    1263 	 */
    1264 	public void setSelectedText(String selectedText)
    1265 	{
    1266 		if(!editable)
    1267 		{
    1268 			throw new InternalError("Text component"
    1269 				+ " read only");
    1270 		}
    1271
    1272 		document.beginCompoundEdit();
    1273
    1274 		try
    1275 		{
    1276 			if(rectSelect)
    1277 			{
    1278 				Element map = document.getDefaultRootElement();
    1279
    1280 				int start = selectionStart - map.getElement(selectionStartLine)
    1281 					.getStartOffset();
    1282 				int end = selectionEnd - map.getElement(selectionEndLine)
    1283 					.getStartOffset();
    1284
    1285 				// Certain rectangles satisfy this condition...
    1286 				if(end < start)
    1287 				{
    1288 					int tmp = end;
    1289 					end = start;
    1290 					start = tmp;
    1291 				}
    1292
    1293 				int lastNewline = 0;
    1294 				int currNewline = 0;
    1295
    1296 				for(int i = selectionStartLine; i <= selectionEndLine; i++)
    1297 				{
    1298 					Element lineElement = map.getElement(i);
    1299 					int lineStart = lineElement.getStartOffset();
    1300 					int lineEnd = lineElement.getEndOffset() - 1;
    1301 					int rectStart = Math.min(lineEnd,lineStart + start);
    1302
    1303 					document.remove(rectStart,Math.min(lineEnd - rectStart,
    1304 						end - start));
    1305
    1306 					if(selectedText == null)
    1307 						continue;
    1308
    1309 					currNewline = selectedText.indexOf('\n',lastNewline);
    1310 					if(currNewline == -1)
    1311 						currNewline = selectedText.length();
    1312
    1313 					document.insertString(rectStart,selectedText
    1314 						.substring(lastNewline,currNewline),null);
    1315
    1316 					lastNewline = Math.min(selectedText.length(),
    1317 						currNewline + 1);
    1318 				}
    1319
    1320 				if(selectedText != null &&
    1321 					currNewline != selectedText.length())
    1322 				{
    1323 					int offset = map.getElement(selectionEndLine)
    1324 						.getEndOffset() - 1;
    1325 					document.insertString(offset,"\n",null);
    1326 					document.insertString(offset + 1,selectedText
    1327 						.substring(currNewline + 1),null);
    1328 				}
    1329 			}
    1330 			else
    1331 			{
    1332 				document.remove(selectionStart,
    1333 					selectionEnd - selectionStart);
    1334 				if(selectedText != null)
    1335 				{
    1336 					document.insertString(selectionStart,
    1337 						selectedText,null);
    1338 				}
    1339 			}
    1340 		}
    1341 		catch(BadLocationException bl)
    1342 		{
    1343 			SoapUI.logError( bl );
    1344 			throw new InternalError("Cannot replace"
    1345 				+ " selection");
    1346 		}
    1347 		// No matter what happends... stops us from leaving document
    1348 		// in a bad state
    1349 		finally
    1350 		{
    1351 			document.endCompoundEdit();
    1352 		}
    1353
    1354 		setCaretPosition(selectionEnd);
    1355 	}
    1356
    1357 	/***
    1358 	 * Returns true if this text area is editable, false otherwise.
    1359 	 */
    1360 	public final boolean isEditable()
    1361 	{
    1362 		return editable;
    1363 	}
    1364
    1365 	/***
    1366 	 * Sets if this component is editable.
    1367 	 * @param editable True if this text area should be editable,
    1368 	 * false otherwise
    1369 	 */
    1370 	public void setEditable(boolean editable)
    1371 	{
    1372 		this.editable = editable;
    1373 	}
    1374
    1375 	/***
    1376 	 * Returns the right click popup menu.
    1377 	 */
    1378 	public final JPopupMenu getRightClickPopup()
    1379 	{
    1380 		return popup;
    1381 	}
    1382
    1383 	/***
    1384 	 * Sets the right click popup menu.
    1385 	 * @param popup The popup
    1386 	 */
    1387 	public final void setRightClickPopup(JPopupMenu popup)
    1388 	{
    1389 		this.popup = popup;
    1390 	}
    1391
    1392 	/***
    1393 	 * Returns the `magic' caret position. This can be used to preserve
    1394 	 * the column position when moving up and down lines.
    1395 	 */
    1396 	public final int getMagicCaretPosition()
    1397 	{
    1398 		return magicCaret;
    1399 	}
    1400
    1401 	/***
    1402 	 * Sets the `magic' caret position. This can be used to preserve
    1403 	 * the column position when moving up and down lines.
    1404 	 * @param magicCaret The magic caret position
    1405 	 */
    1406 	public final void setMagicCaretPosition(int magicCaret)
    1407 	{
    1408 		this.magicCaret = magicCaret;
    1409 	}
    1410
    1411 	/***
    1412 	 * Similar to <code>setSelectedText()</code>, but overstrikes the
    1413 	 * appropriate number of characters if overwrite mode is enabled.
    1414 	 * @param str The string
    1415 	 * @see #setSelectedText(String)
    1416 	 * @see #isOverwriteEnabled()
    1417 	 */
    1418 	public void overwriteSetSelectedText(String str)
    1419 	{
    1420 		// Don't overstrike if there is a selection
    1421 		if(!overwrite || selectionStart != selectionEnd)
    1422 		{
    1423 			setSelectedText(str);
    1424 			return;
    1425 		}
    1426
    1427 		// Don't overstrike if we're on the end of
    1428 		// the line
    1429 		int caret = getCaretPosition();
    1430 		int caretLineEnd = getLineEndOffset(getCaretLine());
    1431 		if(caretLineEnd - caret <= str.length())
    1432 		{
    1433 			setSelectedText(str);
    1434 			return;
    1435 		}
    1436
    1437 		document.beginCompoundEdit();
    1438
    1439 		try
    1440 		{
    1441 			document.remove(caret,str.length());
    1442 			document.insertString(caret,str,null);
    1443 		}
    1444 		catch(BadLocationException bl)
    1445 		{
    1446 			SoapUI.logError( bl );
    1447 		}
    1448 		finally
    1449 		{
    1450 			document.endCompoundEdit();
    1451 		}
    1452 	}
    1453
    1454 	/***
    1455 	 * Returns true if overwrite mode is enabled, false otherwise.
    1456 	 */
    1457 	public final boolean isOverwriteEnabled()
    1458 	{
    1459 		return overwrite;
    1460 	}
    1461
    1462 	/***
    1463 	 * Sets if overwrite mode should be enabled.
    1464 	 * @param overwrite True if overwrite mode should be enabled,
    1465 	 * false otherwise.
    1466 	 */
    1467 	public final void setOverwriteEnabled(boolean overwrite)
    1468 	{
    1469 		this.overwrite = overwrite;
    1470 		painter.invalidateSelectedLines();
    1471 	}
    1472
    1473 	/***
    1474 	 * Returns true if the selection is rectangular, false otherwise.
    1475 	 */
    1476 	public final boolean isSelectionRectangular()
    1477 	{
    1478 		return rectSelect;
    1479 	}
    1480
    1481 	/***
    1482 	 * Sets if the selection should be rectangular.
    1483 	 * @param overwrite True if the selection should be rectangular,
    1484 	 * false otherwise.
    1485 	 */
    1486 	public final void setSelectionRectangular(boolean rectSelect)
    1487 	{
    1488 		this.rectSelect = rectSelect;
    1489 		painter.invalidateSelectedLines();
    1490 	}
    1491
    1492 	/***
    1493 	 * Returns the position of the highlighted bracket (the bracket
    1494 	 * matching the one before the caret)
    1495 	 */
    1496 	public final int getBracketPosition()
    1497 	{
    1498 		return bracketPosition;
    1499 	}
    1500
    1501 	/***
    1502 	 * Returns the line of the highlighted bracket (the bracket
    1503 	 * matching the one before the caret)
    1504 	 */
    1505 	public final int getBracketLine()
    1506 	{
    1507 		return bracketLine;
    1508 	}
    1509
    1510 	/***
    1511 	 * Adds a caret change listener to this text area.
    1512 	 * @param listener The listener
    1513 	 */
    1514 	public final void addCaretListener(CaretListener listener)
    1515 	{
    1516 		listenerList.add(CaretListener.class,listener);
    1517 	}
    1518
    1519 	/***
    1520 	 * Removes a caret change listener from this text area.
    1521 	 * @param listener The listener
    1522 	 */
    1523 	public final void removeCaretListener(CaretListener listener)
    1524 	{
    1525 		listenerList.remove(CaretListener.class,listener);
    1526 	}
    1527
    1528 	/***
    1529 	 * Deletes the selected text from the text area and places it
    1530 	 * into the clipboard.
    1531 	 */
    1532 	public void cut()
    1533 	{
    1534 		if(editable)
    1535 		{
    1536 			copy();
    1537 			setSelectedText("");
    1538 		}
    1539 	}
    1540
    1541 	/***
    1542 	 * Places the selected text into the clipboard.
    1543 	 */
    1544 	public void copy()
    1545 	{
    1546 		if(selectionStart != selectionEnd)
    1547 		{
    1548 			Clipboard clipboard = getToolkit().getSystemClipboard();
    1549
    1550 			String selection = getSelectedText();
    1551
    1552 			int repeatCount = inputHandler.getRepeatCount();
    1553 			StringBuffer buf = new StringBuffer();
    1554 			for(int i = 0; i < repeatCount; i++)
    1555 				buf.append(selection);
    1556
    1557 			clipboard.setContents(new StringSelection(buf.toString()),null);
    1558 		}
    1559 	}
    1560
    1561 	/***
    1562 	 * Inserts the clipboard contents into the text.
    1563 	 */
    1564 	public void paste()
    1565 	{
    1566 		if(editable)
    1567 		{
    1568 			Clipboard clipboard = getToolkit().getSystemClipboard();
    1569 			try
    1570 			{
    1571 				// The MacOS MRJ doesn't convert \r to \n,
    1572 				// so do it here
    1573 				String selection = ((String)clipboard
    1574 					.getContents(this).getTransferData(
    1575 					DataFlavor.stringFlavor))
    1576 					.replace('\r','\n');
    1577
    1578 				int repeatCount = inputHandler.getRepeatCount();
    1579 				StringBuffer buf = new StringBuffer();
    1580 				for(int i = 0; i < repeatCount; i++)
    1581 					buf.append(selection);
    1582 				selection = buf.toString();
    1583 				setSelectedText(selection);
    1584 			}
    1585 			catch(Exception e)
    1586 			{
    1587 				getToolkit().beep();
    1588 				System.err.println("Clipboard does not"
    1589 					+ " contain a string");
    1590 			}
    1591 		}
    1592 	}
    1593
    1594 	/***
    1595 	 * Called by the AWT when this component is removed from it's parent.
    1596 	 * This stops clears the currently focused component.
    1597 	 */
    1598 	public void removeNotify()
    1599 	{
    1600 		super.removeNotify();
    1601 		if(focusedComponentRef != null && focusedComponentRef.get() == this)
    1602 			focusedComponentRef = null;
    1603
    1604 		if( this.document != null )
    1605 			this.document.removeDocumentListener( documentHandler );
    1606 	}
    1607
    1608
    1609
    1610 	@Override
    1611 	public void addNotify()
    1612 	{
    1613 		super.addNotify();
    1614
    1615 		if( this.document != null )
    1616 			this.document.addDocumentListener( documentHandler );
    1617 	}
    1618
    1619 	/***
    1620 	 * Forwards key events directly to the input handler.
    1621 	 * This is slightly faster than using a KeyListener
    1622 	 * because some Swing overhead is avoided.
    1623 	 */
    1624 	public void processKeyEvent(KeyEvent evt)
    1625 	{
    1626 		if(inputHandler == null)
    1627 			return;
    1628 		switch(evt.getID())
    1629 		{
    1630 		case KeyEvent.KEY_TYPED:
    1631 			inputHandler.keyTyped(evt);
    1632 			break;
    1633 		case KeyEvent.KEY_PRESSED:
    1634 			inputHandler.keyPressed(evt);
    1635 			break;
    1636 		case KeyEvent.KEY_RELEASED:
    1637 			inputHandler.keyReleased(evt);
    1638 			break;
    1639 		}
    1640
    1641 		if( !evt.isConsumed() )
    1642 		{
    1643 			KeyListener[] keyListeners = getKeyListeners();
    1644 			for( KeyListener listener : keyListeners )
    1645 			{
    1646 				switch(evt.getID())
    1647 				{
    1648 				case KeyEvent.KEY_TYPED:
    1649 					listener.keyTyped(evt);
    1650 					break;
    1651 				case KeyEvent.KEY_PRESSED:
    1652 					listener.keyPressed(evt);
    1653 					break;
    1654 				case KeyEvent.KEY_RELEASED:
    1655 					listener.keyReleased(evt);
    1656 					break;
    1657 				}
    1658
    1659 				if( evt.isConsumed() )
    1660 					break;
    1661 			}
    1662
    1663 		}
    1664 	}
    1665
    1666 	// protected members
    1667 	protected static final String CENTER = "center";
    1668 	protected static final String RIGHT = "right";
    1669 	protected static final String BOTTOM = "bottom";
    1670
    1671 	protected static WeakReference<JEditTextArea> focusedComponentRef;
    1672 	protected static final Timer caretTimer;
    1673
    1674 	protected TextAreaPainter painter;
    1675
    1676 	protected JPopupMenu popup;
    1677
    1678 	protected EventListenerList listenerList;
    1679 	protected MutableCaretEvent caretEvent;
    1680
    1681 	protected boolean caretBlinks;
    1682 	protected boolean caretVisible;
    1683 	protected boolean blink;
    1684
    1685 	protected boolean editable;
    1686
    1687 	protected int firstLine;
    1688 	protected int visibleLines;
    1689 //	protected int electricScroll;
    1690
    1691 //	protected int horizontalOffset;
    1692
    1693 	//protected JScrollBar vertical;
    1694 	//protected JScrollBar horizontal;
    1695 	protected boolean scrollBarsInitialized;
    1696
    1697 	protected InputHandler inputHandler;
    1698 	protected SyntaxDocument document;
    1699 	protected DocumentHandler documentHandler;
    1700
    1701 	protected Segment lineSegment;
    1702
    1703 	protected int selectionStart;
    1704 	protected int selectionStartLine;
    1705 	protected int selectionEnd;
    1706 	protected int selectionEndLine;
    1707 	protected boolean biasLeft;
    1708
    1709 	protected int bracketPosition;
    1710 	protected int bracketLine;
    1711
    1712 	protected int magicCaret;
    1713 	protected boolean overwrite;
    1714 	protected boolean rectSelect;
    1715
    1716 	protected void fireCaretEvent()
    1717 	{
    1718 		Object[] listeners = listenerList.getListenerList();
    1719 		for(int i = listeners.length - 2; i >= 0; i--)
    1720 		{
    1721 			if(listeners[i] == CaretListener.class)
    1722 			{
    1723 				((CaretListener)listeners[i+1]).caretUpdate(caretEvent);
    1724 			}
    1725 		}
    1726 	}
    1727
    1728 	protected void updateBracketHighlight(int newCaretPosition)
    1729 	{
    1730 		if(newCaretPosition == 0)
    1731 		{
    1732 			bracketPosition = bracketLine = -1;
    1733 			return;
    1734 		}
    1735
    1736 		try
    1737 		{
    1738 			int offset = TextUtilities.findMatchingBracket(
    1739 				document,newCaretPosition - 1);
    1740 			if(offset != -1)
    1741 			{
    1742 				bracketLine = getLineOfOffset(offset);
    1743 				bracketPosition = offset - getLineStartOffset(bracketLine);
    1744 				return;
    1745 			}
    1746 		}
    1747 		catch(BadLocationException bl)
    1748 		{
    1749 			SoapUI.logError( bl );
    1750 		}
    1751
    1752 		bracketLine = bracketPosition = -1;
    1753 	}
    1754
    1755 	protected void documentChanged(DocumentEvent evt)
    1756 	{
    1757 		DocumentEvent.ElementChange ch = evt.getChange(
    1758 			document.getDefaultRootElement());
    1759
    1760 		int count;
    1761 		if(ch == null)
    1762 			count = 0;
    1763 		else
    1764 			count = ch.getChildrenAdded().length -
    1765 				ch.getChildrenRemoved().length;
    1766
    1767 		int line = getLineOfOffset(evt.getOffset());
    1768 		if(count == 0)
    1769 		{
    1770 			painter.invalidateLine(line);
    1771 		}
    1772 		// do magic stuff
    1773 		else if(line < firstLine)
    1774 		{
    1775 			setFirstLine(firstLine + count);
    1776 		}
    1777 		// end of magic stuff
    1778 		else
    1779 		{
    1780 			painter.invalidateLineRange(line,firstLine + visibleLines);
    1781 			updateScrollBars();
    1782 		}
    1783 	}
    1784
    1785 	class ScrollLayout implements LayoutManager
    1786 	{
    1787 		public void addLayoutComponent(String name, Component comp)
    1788 		{
    1789 			if(name.equals(CENTER))
    1790 				center = comp;
    1791 			/*
    1792 			else if(name.equals(RIGHT))
    1793 				right = comp;
    1794 			else if(name.equals(BOTTOM))
    1795 				bottom = comp;
    1796 			else if(name.equals(LEFT_OF_SCROLLBAR))
    1797 				leftOfScrollBar.addElement(comp);*/
    1798 		}
    1799
    1800 		public void removeLayoutComponent(Component comp)
    1801 		{
    1802 			if(center == comp)
    1803 				center = null;
    1804 			/*
    1805 			if(right == comp)
    1806 				right = null;
    1807 			if(bottom == comp)
    1808 				bottom = null;
    1809 			else
    1810 				leftOfScrollBar.removeElement(comp);*/
    1811 		}
    1812
    1813 		public Dimension preferredLayoutSize(Container parent)
    1814 		{
    1815 			Dimension dim = new Dimension();
    1816 			Insets insets = getInsets();
    1817 			dim.width = insets.left + insets.right;
    1818 			dim.height = insets.top + insets.bottom;
    1819
    1820 			Dimension centerPref = center.getPreferredSize();
    1821 			dim.width += centerPref.width;
    1822 			dim.height += centerPref.height;
    1823 			/*
    1824 			Dimension rightPref = right.getPreferredSize();
    1825 			dim.width += rightPref.width;
    1826 			Dimension bottomPref = bottom.getPreferredSize();
    1827 			dim.height += bottomPref.height;
    1828 		*/
    1829 			return dim;
    1830 		}
    1831
    1832 		public Dimension minimumLayoutSize(Container parent)
    1833 		{
    1834 			Dimension dim = new Dimension();
    1835 			Insets insets = getInsets();
    1836 			dim.width = insets.left + insets.right;
    1837 			dim.height = insets.top + insets.bottom;
    1838
    1839 			Dimension centerPref = center.getMinimumSize();
    1840 			dim.width += centerPref.width;
    1841 			dim.height += centerPref.height;
    1842 			/*
    1843 			Dimension rightPref = right.getMinimumSize();
    1844 			dim.width += rightPref.width;
    1845 			Dimension bottomPref = bottom.getMinimumSize();
    1846 			dim.height += bottomPref.height;
    1847 */
    1848 			return dim;
    1849 		}
    1850
    1851 		public void layoutContainer(Container parent)
    1852 		{
    1853 			Dimension size = parent.getSize();
    1854 			Insets insets = parent.getInsets();
    1855 			int itop = insets.top;
    1856 			int ileft = insets.left;
    1857 			int ibottom = insets.bottom;
    1858 			int iright = insets.right;
    1859
    1860 			//int rightWidth = right.getPreferredSize().width;
    1861 			//int bottomHeight = bottom.getPreferredSize().height;
    1862 			int centerWidth = size.width - ileft - iright;
    1863 			int centerHeight = size.height  - itop - ibottom;
    1864
    1865 			center.setBounds(
    1866 				ileft,
    1867 				itop,
    1868 				centerWidth,
    1869 				centerHeight);
    1870
    1871 /*			right.setBounds(
    1872 				ileft + centerWidth,
    1873 				itop,
    1874 				rightWidth,
    1875 				centerHeight);
    1876
    1877 			// Lay out all status components, in order
    1878 			Enumeration status = leftOfScrollBar.elements();
    1879 			while(status.hasMoreElements())
    1880 			{
    1881 				Component comp = (Component)status.nextElement();
    1882 				Dimension dim = comp.getPreferredSize();
    1883 				comp.setBounds(ileft,
    1884 					itop + centerHeight,
    1885 					dim.width,
    1886 					bottomHeight);
    1887 				ileft += dim.width;
    1888 			}
    1889
    1890 			bottom.setBounds(
    1891 				ileft,
    1892 				itop + centerHeight,
    1893 				size.width - rightWidth - ileft - iright,
    1894 				bottomHeight);
    1895 				*/
    1896 		}
    1897
    1898 		// private members
    1899 		private Component center;
    1900 		//private Component right;
    1901 		//private Component bottom;
    1902 		//private Vector<Component> leftOfScrollBar = new Vector<Component>();
    1903 	}
    1904
    1905 	static class CaretBlinker implements ActionListener
    1906 	{
    1907 		public void actionPerformed(ActionEvent evt)
    1908 		{
    1909 			if(focusedComponentRef != null && focusedComponentRef.get() != null
    1910 				&& focusedComponentRef.get().hasFocus())
    1911 				focusedComponentRef.get().blinkCaret();
    1912 		}
    1913 	}
    1914
    1915 	class MutableCaretEvent extends CaretEvent
    1916 	{
    1917 		MutableCaretEvent()
    1918 		{
    1919 			super(JEditTextArea.this);
    1920 		}
    1921
    1922 		public int getDot()
    1923 		{
    1924 			return getCaretPosition();
    1925 		}
    1926
    1927 		public int getMark()
    1928 		{
    1929 			return getMarkPosition();
    1930 		}
    1931 	}
    1932
    1933 	class AdjustHandler implements AdjustmentListener
    1934 	{
    1935 		public void adjustmentValueChanged(final AdjustmentEvent evt)
    1936 		{
    1937 			if(!scrollBarsInitialized)
    1938 				return;
    1939
    1940 			// If this is not done, mousePressed events accumilate
    1941 			// and the result is that scrolling doesn't stop after
    1942 			// the mouse is released
    1943 			SwingUtilities.invokeLater(new Runnable() {
    1944 				public void run()
    1945 				{
    1946 					/*
    1947 					if(evt.getAdjustable() == vertical)
    1948 						setFirstLine(vertical.getValue());
    1949 					else
    1950 						setHorizontalOffset(-horizontal.getValue());*/
    1951 				}
    1952 			});
    1953 		}
    1954 	}
    1955
    1956 	class ComponentHandler extends ComponentAdapter
    1957 	{
    1958 		public void componentResized(ComponentEvent evt)
    1959 		{
    1960 			recalculateVisibleLines();
    1961 			scrollBarsInitialized = true;
    1962 		}
    1963 	}
    1964
    1965 	class DocumentHandler implements DocumentListener
    1966 	{
    1967 		public void insertUpdate(DocumentEvent evt)
    1968 		{
    1969 			documentChanged(evt);
    1970
    1971 			int offset = evt.getOffset();
    1972 			int length = evt.getLength();
    1973
    1974 			int newStart;
    1975 			int newEnd;
    1976
    1977 			if(selectionStart > offset || (selectionStart
    1978 				== selectionEnd && selectionStart == offset))
    1979 				newStart = selectionStart + length;
    1980 			else
    1981 				newStart = selectionStart;
    1982
    1983 			if(selectionEnd >= offset)
    1984 				newEnd = selectionEnd + length;
    1985 			else
    1986 				newEnd = selectionEnd;
    1987
    1988 			select(newStart,newEnd);
    1989 		}
    1990
    1991 		public void removeUpdate(DocumentEvent evt)
    1992 		{
    1993 			documentChanged(evt);
    1994
    1995 			int offset = evt.getOffset();
    1996 			int length = evt.getLength();
    1997
    1998 			int newStart;
    1999 			int newEnd;
    2000
    2001 			if(selectionStart > offset)
    2002 			{
    2003 				if(selectionStart > offset + length)
    2004 					newStart = selectionStart - length;
    2005 				else
    2006 					newStart = offset;
    2007 			}
    2008 			else
    2009 				newStart = selectionStart;
    2010
    2011 			if(selectionEnd > offset)
    2012 			{
    2013 				if(selectionEnd > offset + length)
    2014 					newEnd = selectionEnd - length;
    2015 				else
    2016 					newEnd = offset;
    2017 			}
    2018 			else
    2019 				newEnd = selectionEnd;
    2020
    2021 			select(newStart,newEnd);
    2022 		}
    2023
    2024 		public void changedUpdate(DocumentEvent evt)
    2025 		{
    2026 		}
    2027 	}
    2028
    2029 	class DragHandler implements MouseMotionListener
    2030 	{
    2031 		public void mouseDragged(MouseEvent evt)
    2032 		{
    2033 			if(popup != null && popup.isVisible())
    2034 				return;
    2035
    2036 			setSelectionRectangular((evt.getModifiers()
    2037 				& InputEvent.CTRL_MASK) != 0);
    2038 			select(getMarkPosition(),xyToOffset(evt.getX(),evt.getY()));
    2039 		}
    2040
    2041 		public void mouseMoved(MouseEvent evt) {}
    2042 	}
    2043
    2044 	class FocusHandler implements FocusListener
    2045 	{
    2046 		public void focusGained(FocusEvent evt)
    2047 		{
    2048 			if( isEditable() )
    2049 				setCaretVisible(true);
    2050 			focusedComponentRef = new WeakReference<JEditTextArea>( JEditTextArea.this );
    2051 		}
    2052
    2053 		public void focusLost(FocusEvent evt)
    2054 		{
    2055 			setCaretVisible(false);
    2056 			focusedComponentRef = null;
    2057 		}
    2058 	}
    2059
    2060 	class MouseHandler extends MouseAdapter
    2061 	{
    2062 		@Override
    2063 		public void mouseClicked( MouseEvent e )
    2064 		{
    2065 			if( popup != null && e.isPopupTrigger() )
    2066 			{
    2067 				doPopup(e);
    2068 			}
    2069 		}
    2070
    2071 		private void doPopup(MouseEvent evt)
    2072 		{
    2073 			popup.show(painter,evt.getX(),evt.getY());
    2074 		}
    2075
    2076 		@Override
    2077 		public void mouseReleased( MouseEvent e )
    2078 		{
    2079 			if( popup != null && e.isPopupTrigger() )
    2080 			{
    2081 				doPopup(e);
    2082 			}
    2083 		}
    2084
    2085 		public void mousePressed(MouseEvent evt)
    2086 		{
    2087 			requestFocus();
    2088
    2089 			// Focus events not fired sometimes?
    2090 			if( isEditable() )
    2091 				setCaretVisible(true);
    2092
    2093 			focusedComponentRef = new WeakReference<JEditTextArea>( JEditTextArea.this );
    2094
    2095 			if( popup != null && evt.isPopupTrigger() )
    2096 			{
    2097 				doPopup( evt );
    2098 				return;
    2099 			}
    2100
    2101 			if( evt.getButton() != MouseEvent.BUTTON1 )
    2102 			{
    2103 				return;
    2104 			}
    2105
    2106 			int line = yToLine(evt.getY());
    2107 			int offset = xToOffset(line,evt.getX());
    2108 			int dot = getLineStartOffset(line) + offset;
    2109
    2110 			switch(evt.getClickCount())
    2111 			{
    2112 			case 1:
    2113 				doSingleClick(evt,line,offset,dot);
    2114 				break;
    2115 			case 2:
    2116 				// It uses the bracket matching stuff, so
    2117 				// it can throw a BLE
    2118 				try
    2119 				{
    2120 					doDoubleClick(evt,line,offset,dot);
    2121 				}
    2122 				catch(BadLocationException bl)
    2123 				{
    2124 					SoapUI.logError( bl );
    2125 				}
    2126 				break;
    2127 			case 3:
    2128 				doTripleClick(evt,line,offset,dot);
    2129 				break;
    2130 			}
    2131 		}
    2132
    2133 		private void doSingleClick(MouseEvent evt, int line,
    2134 			int offset, int dot)
    2135 		{
    2136 			if((evt.getModifiers() & InputEvent.SHIFT_MASK) != 0)
    2137 			{
    2138 				rectSelect = (evt.getModifiers() & InputEvent.CTRL_MASK) != 0;
    2139 				select(getMarkPosition(),dot);
    2140 			}
    2141 			else
    2142 				setCaretPosition(dot);
    2143 		}
    2144
    2145 		private void doDoubleClick(MouseEvent evt, int line,
    2146 			int offset, int dot) throws BadLocationException
    2147 		{
    2148 			// Ignore empty lines
    2149 			if(getLineLength(line) == 0)
    2150 				return;
    2151
    2152 			try
    2153 			{
    2154 				int bracket = TextUtilities.findMatchingBracket(
    2155 					document,Math.max(0,dot - 1));
    2156 				if(bracket != -1)
    2157 				{
    2158 					int mark = getMarkPosition();
    2159 					// Hack
    2160 					if(bracket > mark)
    2161 					{
    2162 						bracket++;
    2163 						mark--;
    2164 					}
    2165 					select(mark,bracket);
    2166 					return;
    2167 				}
    2168 			}
    2169 			catch(BadLocationException bl)
    2170 			{
    2171 				SoapUI.logError( bl );
    2172 			}
    2173
    2174 			// Ok, it's not a bracket... select the word
    2175 			String lineText = getLineText(line);
    2176 			char ch = lineText.charAt(Math.max(0,offset - 1));
    2177
    2178 			String noWordSep = (String)document.getProperty("noWordSep");
    2179 			if(noWordSep == null)
    2180 				noWordSep = "";
    2181
    2182 			// If the user clicked on a non-letter char,
    2183 			// we select the surrounding non-letters
    2184 			boolean selectNoLetter = (!Character
    2185 				.isLetterOrDigit(ch)
    2186 				&& noWordSep.indexOf(ch) == -1);
    2187
    2188 			int wordStart = 0;
    2189
    2190 			for(int i = offset - 1; i >= 0; i--)
    2191 			{
    2192 				ch = lineText.charAt(i);
    2193 				if(selectNoLetter ^ (!Character
    2194 					.isLetterOrDigit(ch) &&
    2195 					noWordSep.indexOf(ch) == -1))
    2196 				{
    2197 					wordStart = i + 1;
    2198 					break;
    2199 				}
    2200 			}
    2201
    2202 			int wordEnd = lineText.length();
    2203 			for(int i = offset; i < lineText.length(); i++)
    2204 			{
    2205 				ch = lineText.charAt(i);
    2206 				if(selectNoLetter ^ (!Character
    2207 					.isLetterOrDigit(ch) &&
    2208 					noWordSep.indexOf(ch) == -1))
    2209 				{
    2210 					wordEnd = i;
    2211 					break;
    2212 				}
    2213 			}
    2214
    2215 			int lineStart = getLineStartOffset(line);
    2216 			select(lineStart + wordStart,lineStart + wordEnd);
    2217
    2218 			/*
    2219 			String lineText = getLineText(line);
    2220 			String noWordSep = (String)document.getProperty("noWordSep");
    2221 			int wordStart = TextUtilities.findWordStart(lineText,offset,noWordSep);
    2222 			int wordEnd = TextUtilities.findWordEnd(lineText,offset,noWordSep);
    2223
    2224 			int lineStart = getLineStartOffset(line);
    2225 			select(lineStart + wordStart,lineStart + wordEnd);
    2226 			*/
    2227 		}
    2228
    2229 		private void doTripleClick(MouseEvent evt, int line,
    2230 			int offset, int dot)
    2231 		{
    2232 			select(getLineStartOffset(line),getLineEndOffset(line)-1);
    2233 		}
    2234 	}
    2235
    2236 	class CaretUndo extends AbstractUndoableEdit
    2237 	{
    2238 		private int start;
    2239 		private int end;
    2240
    2241 		CaretUndo(int start, int end)
    2242 		{
    2243 			this.start = start;
    2244 			this.end = end;
    2245 		}
    2246
    2247 		public boolean isSignificant()
    2248 		{
    2249 			return false;
    2250 		}
    2251
    2252 		public String getPresentationName()
    2253 		{
    2254 			return "caret move";
    2255 		}
    2256
    2257 		public void undo() throws CannotUndoException
    2258 		{
    2259 			super.undo();
    2260
    2261 			select(start,end);
    2262 		}
    2263
    2264 		public void redo() throws CannotRedoException
    2265 		{
    2266 			super.redo();
    2267
    2268 			select(start,end);
    2269 		}
    2270
    2271 		public boolean addEdit(UndoableEdit edit)
    2272 		{
    2273 			if(edit instanceof CaretUndo)
    2274 			{
    2275 				CaretUndo cedit = (CaretUndo)edit;
    2276 				start = cedit.start;
    2277 				end = cedit.end;
    2278 				cedit.die();
    2279
    2280 				return true;
    2281 			}
    2282 			else
    2283 				return false;
    2284 		}
    2285 	}
    2286
    2287 	static
    2288 	{
    2289 		caretTimer = new Timer(500,new CaretBlinker());
    2290 		caretTimer.setInitialDelay(500);
    2291 		caretTimer.start();
    2292 	}
    2293
    2294 	public Dimension getPreferredSize()
    2295 	{
    2296 		Dimension preferredSize = painter.getPreferredSize();
    2297
    2298 		if( getParent() instanceof JViewport )
    2299 		{
    2300 			JViewport viewport = ( JViewport ) getParent();
    2301 			Dimension size = viewport.getSize();
    2302
    2303 			preferredSize = new Dimension(
    2304 						(int)(preferredSize.getWidth() < size.getWidth() ? size.getWidth() : preferredSize.getWidth()),
    2305 						(int)(preferredSize.getHeight() < size.getHeight() ? size.getHeight() : preferredSize.getHeight()) );
    2306 		}
    2307
    2308 		return preferredSize;
    2309 	}
    2310
    2311 	public Dimension getMaximumSize()
    2312 	{
    2313 		return painter.getMaximumSize();
    2314 	}
    2315
    2316 	public Dimension getMinimumSize()
    2317 	{
    2318 		return painter.getMinimumSize();
    2319 	}
    2320
    2321 	public int getMaxLineLength()
    2322 	{
    2323 		int max = 0;
    2324
    2325 		for( int c = 0; c < getLineCount(); c++ )
    2326 		{
    2327 			if( getLineLength( c ) > max )
    2328 				max = getLineLength( c );
    2329 		}
    2330
    2331 		return max;
    2332 	}
    2333
    2334 	public Dimension getPreferredScrollableViewportSize()
    2335 	{
    2336 		return getPreferredSize();
    2337 	}
    2338
    2339 	public int getScrollableBlockIncrement( Rectangle arg0, int arg1, int arg2 )
    2340 	{
    2341 		return getFontMetrics( getFont() ).getHeight()*5;
    2342 	}
    2343
    2344 	public boolean getScrollableTracksViewportHeight()
    2345 	{
    2346 		return false;
    2347 	}
    2348
    2349 	public boolean getScrollableTracksViewportWidth()
    2350 	{
    2351 		return false;
    2352 	}
    2353
    2354 	public int getScrollableUnitIncrement( Rectangle arg0, int arg1, int arg2 )
    2355 	{
    2356 		return getFontMetrics( getFont() ).getHeight();
    2357 	}
    2358 }