View Javadoc

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