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