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