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