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