View Javadoc

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