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