KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jedit > syntax > JEditTextArea


1 package org.jedit.syntax;
2
3 /*
4  * JEditTextArea.java - jEdit's text component
5  * Copyright (C) 1999 Slava Pestov
6  *
7  * You may use and modify this package for any purpose. Redistribution is
8  * permitted, in both source and binary form, provided that this notice
9  * remains intact in all source distributions of this package.
10  */

11
12 import javax.swing.border.*;
13 import javax.swing.event.*;
14 import javax.swing.text.*;
15 import javax.swing.undo.*;
16 import javax.swing.*;
17 import java.awt.datatransfer.*;
18 import java.awt.event.*;
19 import java.awt.*;
20 import java.util.Enumeration JavaDoc;
21 import java.util.Vector JavaDoc;
22
23 /**
24  * jEdit's text area component. It is more suited for editing program
25  * source code than JEditorPane, because it drops the unnecessary features
26  * (images, variable-width lines, and so on) and adds a whole bunch of
27  * useful goodies such as:
28  * <ul>
29  * <li>More flexible key binding scheme
30  * <li>Supports macro recorders
31  * <li>Rectangular selection
32  * <li>Bracket highlighting
33  * <li>Syntax highlighting
34  * <li>Command repetition
35  * <li>Block caret can be enabled
36  * </ul>
37  * It is also faster and doesn't have as many problems. It can be used
38  * in other applications; the only other part of jEdit it depends on is
39  * the syntax package.<p>
40  *
41  * To use it in your app, treat it like any other component, for example:
42  * <pre>JEditTextArea ta = new JEditTextArea();
43  * ta.setTokenMarker(new JavaTokenMarker());
44  * ta.setText("public class Test {\n"
45  * + " public static void main(String[] args) {\n"
46  * + " System.out.println(\"Hello World\");\n"
47  * + " }\n"
48  * + "}");</pre>
49  *
50  * @author Slava Pestov
51  * @version $Id: JEditTextArea.java,v 1.1 2003/12/14 16:29:49 daggerrz Exp $
52  */

53 public class JEditTextArea extends JComponent {
54     /**
55      * Adding components with this name to the text area will place
56      * them left of the horizontal scroll bar. In jEdit, the status
57      * bar is added this way.
58      */

59     public static String JavaDoc LEFT_OF_SCROLLBAR = "los";
60
61     /**
62      * Creates a new JEditTextArea with the default settings.
63      */

64     public JEditTextArea() {
65         this(TextAreaDefaults.getDefaults());
66     }
67
68     /**
69      * Creates a new JEditTextArea with the specified settings.
70      * @param defaults The default settings
71      */

72     public JEditTextArea(TextAreaDefaults defaults) {
73         // Enable the necessary events
74
enableEvents(AWTEvent.KEY_EVENT_MASK);
75
76         // Initialize some misc. stuff
77
painter = new TextAreaPainter(this, defaults);
78         documentHandler = new DocumentHandler();
79         listenerList = new EventListenerList();
80         caretEvent = new MutableCaretEvent();
81         lineSegment = new Segment();
82         bracketLine = bracketPosition = -1;
83         blink = true;
84
85         // Initialize the GUI
86
setLayout(new ScrollLayout());
87         add(CENTER, painter);
88         add(RIGHT, vertical = new JScrollBar(JScrollBar.VERTICAL));
89         add(BOTTOM, horizontal = new JScrollBar(JScrollBar.HORIZONTAL));
90
91         vertical.putClientProperty("JScrollBar.isFreeStanding", Boolean.FALSE);
92         horizontal.putClientProperty("JScrollBar.isFreeStanding", Boolean.FALSE);
93
94         // Add some event listeners
95
vertical.addAdjustmentListener(new AdjustHandler());
96         horizontal.addAdjustmentListener(new AdjustHandler());
97         painter.addComponentListener(new ComponentHandler());
98         painter.addMouseListener(new MouseHandler());
99         painter.addMouseMotionListener(new DragHandler());
100         addFocusListener(new FocusHandler());
101         painter.addMouseWheelListener(new ScrollWheelHandler());
102
103         // Load the defaults
104
setInputHandler(defaults.inputHandler);
105         setDocument(defaults.document);
106         editable = defaults.editable;
107         caretVisible = defaults.caretVisible;
108         caretBlinks = defaults.caretBlinks;
109         electricScroll = defaults.electricScroll;
110
111         popup = defaults.popup;
112
113         // We don't seem to get the initial focus event?
114
focusedComponent = this;
115     }
116
117     /**
118      * Returns if this component can be traversed by pressing
119      * the Tab key. This returns false.
120      */

121     public final boolean isManagingFocus() {
122         return true;
123     }
124
125     /**
126      * Returns the object responsible for painting this text area.
127      */

128     public final TextAreaPainter getPainter() {
129         return painter;
130     }
131
132     /**
133      * Returns the input handler.
134      */

135     public final InputHandler getInputHandler() {
136         return inputHandler;
137     }
138
139     /**
140      * Sets the input handler.
141      * @param inputHandler The new input handler
142      */

143     public void setInputHandler(InputHandler inputHandler) {
144         this.inputHandler = inputHandler;
145     }
146
147     /**
148      * Returns true if the caret is blinking, false otherwise.
149      */

150     public final boolean isCaretBlinkEnabled() {
151         return caretBlinks;
152     }
153
154     /**
155      * Toggles caret blinking.
156      * @param caretBlinks True if the caret should blink, false otherwise
157      */

158     public void setCaretBlinkEnabled(boolean caretBlinks) {
159         this.caretBlinks = caretBlinks;
160         if (!caretBlinks)
161             blink = false;
162
163         painter.invalidateSelectedLines();
164     }
165
166     /**
167      * Returns true if the caret is visible, false otherwise.
168      */

169     public final boolean isCaretVisible() {
170         return (!caretBlinks || blink) && caretVisible;
171     }
172
173     /**
174      * Sets if the caret should be visible.
175      * @param caretVisible True if the caret should be visible, false
176      * otherwise
177      */

178     public void setCaretVisible(boolean caretVisible) {
179         this.caretVisible = caretVisible;
180         blink = true;
181
182         painter.invalidateSelectedLines();
183     }
184
185     /**
186      * Blinks the caret.
187      */

188     public final void blinkCaret() {
189         if (caretBlinks) {
190             blink = !blink;
191             painter.invalidateSelectedLines();
192         } else
193             blink = true;
194     }
195
196     /**
197      * Returns the number of lines from the top and button of the
198      * text area that are always visible.
199      */

200     public final int getElectricScroll() {
201         return electricScroll;
202     }
203
204     /**
205      * Sets the number of lines from the top and bottom of the text
206      * area that are always visible
207      * @param electricScroll The number of lines always visible from
208      * the top or bottom
209      */

210     public final void setElectricScroll(int electricScroll) {
211         this.electricScroll = electricScroll;
212     }
213
214     /**
215      * Updates the state of the scroll bars. This should be called
216      * if the number of lines in the document changes, or when the
217      * size of the text are changes.
218      */

219     public void updateScrollBars() {
220         if (vertical != null && visibleLines != 0) {
221             vertical.setValues(firstLine, visibleLines, 0, getLineCount());
222             vertical.setUnitIncrement(2);
223             vertical.setBlockIncrement(visibleLines);
224         }
225
226         int width = painter.getWidth();
227         if (horizontal != null && width != 0) {
228             horizontal.setValues(-horizontalOffset, width, 0, width * 5);
229             horizontal.setUnitIncrement(painter.getFontMetrics().charWidth('w'));
230             horizontal.setBlockIncrement(width / 2);
231         }
232     }
233
234     /**
235      * Returns the line displayed at the text area's origin.
236      */

237     public final int getFirstLine() {
238         return firstLine;
239     }
240
241     /**
242      * Sets the line displayed at the text area's origin without
243      * updating the scroll bars.
244      */

245     public void setFirstLine(int firstLine) {
246         if (firstLine == this.firstLine)
247             return;
248         int oldFirstLine = this.firstLine;
249         this.firstLine = firstLine;
250         if (firstLine != vertical.getValue())
251             updateScrollBars();
252         painter.repaint();
253     }
254
255     /**
256      * Returns the number of lines visible in this text area.
257      */

258     public final int getVisibleLines() {
259         return visibleLines;
260     }
261
262     /**
263      * Recalculates the number of visible lines. This should not
264      * be called directly.
265      */

266     public final void recalculateVisibleLines() {
267         if (painter == null)
268             return;
269         int height = painter.getHeight();
270         int lineHeight = painter.getFontMetrics().getHeight();
271         int oldVisibleLines = visibleLines;
272         visibleLines = height / lineHeight;
273         updateScrollBars();
274     }
275
276     /**
277      * Returns the horizontal offset of drawn lines.
278      */

279     public final int getHorizontalOffset() {
280         return horizontalOffset;
281     }
282
283     /**
284      * Sets the horizontal offset of drawn lines. This can be used to
285      * implement horizontal scrolling.
286      * @param horizontalOffset offset The new horizontal offset
287      */

288     public void setHorizontalOffset(int horizontalOffset) {
289         if (horizontalOffset == this.horizontalOffset)
290             return;
291         this.horizontalOffset = horizontalOffset;
292         if (horizontalOffset != horizontal.getValue())
293             updateScrollBars();
294         painter.repaint();
295     }
296
297     /**
298      * A fast way of changing both the first line and horizontal
299      * offset.
300      * @param firstLine The new first line
301      * @param horizontalOffset The new horizontal offset
302      * @return True if any of the values were changed, false otherwise
303      */

304     public boolean setOrigin(int firstLine, int horizontalOffset) {
305         boolean changed = false;
306         int oldFirstLine = this.firstLine;
307
308         if (horizontalOffset != this.horizontalOffset) {
309             this.horizontalOffset = horizontalOffset;
310             changed = true;
311         }
312
313         if (firstLine != this.firstLine) {
314             this.firstLine = firstLine;
315             changed = true;
316         }
317
318         if (changed) {
319             updateScrollBars();
320             painter.repaint();
321         }
322
323         return changed;
324     }
325
326     /**
327      * Ensures that the caret is visible by scrolling the text area if
328      * necessary.
329      * @return True if scrolling was actually performed, false if the
330      * caret was already visible
331      */

332     public boolean scrollToCaret() {
333         int line = getCaretLine();
334         int lineStart = getLineStartOffset(line);
335         int offset = Math.max(0, Math.min(getLineLength(line) - 1, getCaretPosition() - lineStart));
336
337         return scrollTo(line, offset);
338     }
339
340     /**
341      * Ensures that the specified line and offset is visible by scrolling
342      * the text area if necessary.
343      * @param line The line to scroll to
344      * @param offset The offset in the line to scroll to
345      * @return True if scrolling was actually performed, false if the
346      * line and offset was already visible
347      */

348     public boolean scrollTo(int line, int offset) {
349         // visibleLines == 0 before the component is realized
350
// we can't do any proper scrolling then, so we have
351
// this hack...
352
if (visibleLines == 0) {
353             setFirstLine(Math.max(0, line - electricScroll));
354             return true;
355         }
356
357         int newFirstLine = firstLine;
358         int newHorizontalOffset = horizontalOffset;
359
360         if (line < firstLine + electricScroll) {
361             newFirstLine = Math.max(0, line - electricScroll);
362         } else if (line + electricScroll >= firstLine + visibleLines) {
363             newFirstLine = (line - visibleLines) + electricScroll + 1;
364             if (newFirstLine + visibleLines >= getLineCount())
365                 newFirstLine = getLineCount() - visibleLines;
366             if (newFirstLine < 0)
367                 newFirstLine = 0;
368         }
369
370         int x = _offsetToX(line, offset);
371         int width = painter.getFontMetrics().charWidth('w');
372
373         if (x < 0) {
374             newHorizontalOffset = Math.min(0, horizontalOffset - x + width + 5);
375         } else if (x + width >= painter.getWidth()) {
376             newHorizontalOffset = horizontalOffset + (painter.getWidth() - x) - width - 5;
377         }
378
379         return setOrigin(newFirstLine, newHorizontalOffset);
380     }
381
382     /**
383      * Converts a line index to a y co-ordinate.
384      * @param line The line
385      */

386     public int lineToY(int line) {
387         FontMetrics fm = painter.getFontMetrics();
388         return (line - firstLine) * fm.getHeight() - (fm.getLeading() + fm.getMaxDescent());
389     }
390
391     /**
392      * Converts a y co-ordinate to a line index.
393      * @param y The y co-ordinate
394      */

395     public int yToLine(int y) {
396         FontMetrics fm = painter.getFontMetrics();
397         int height = fm.getHeight();
398         return Math.max(0, Math.min(getLineCount() - 1, y / height + firstLine));
399     }
400
401     /**
402      * Converts an offset in a line into an x co-ordinate. This is a
403      * slow version that can be used any time.
404      * @param line The line
405      * @param offset The offset, from the start of the line
406      */

407     public final int offsetToX(int line, int offset) {
408         // don't use cached tokens
409
painter.currentLineTokens = null;
410         return _offsetToX(line, offset);
411     }
412
413     /**
414      * Converts an offset in a line into an x co-ordinate. This is a
415      * fast version that should only be used if no changes were made
416      * to the text since the last repaint.
417      * @param line The line
418      * @param offset The offset, from the start of the line
419      */

420     public int _offsetToX(int line, int offset) {
421         TokenMarker tokenMarker = getTokenMarker();
422
423         /* Use painter's cached info for speed */
424         FontMetrics fm = painter.getFontMetrics();
425
426         getLineText(line, lineSegment);
427
428         int segmentOffset = lineSegment.offset;
429         int x = horizontalOffset;
430
431         /* If syntax coloring is disabled, do simple translation */
432         if (tokenMarker == null) {
433             lineSegment.count = offset;
434             return x + Utilities.getTabbedTextWidth(lineSegment, fm, x, painter, 0);
435         }
436         /* If syntax coloring is enabled, we have to do this because
437          * tokens can vary in width */

438         else {
439             Token tokens;
440             if (painter.currentLineIndex == line && painter.currentLineTokens != null)
441                 tokens = painter.currentLineTokens;
442             else {
443                 painter.currentLineIndex = line;
444                 tokens = painter.currentLineTokens = tokenMarker.markTokens(lineSegment, line);
445             }
446
447             Toolkit toolkit = painter.getToolkit();
448             Font defaultFont = painter.getFont();
449             SyntaxStyle[] styles = painter.getStyles();
450
451             for (;;) {
452                 byte id = tokens.id;
453                 if (id == Token.END) {
454                     return x;
455                 }
456
457                 if (id == Token.NULL)
458                     fm = painter.getFontMetrics();
459                 else
460                     fm = styles[id].getFontMetrics(defaultFont);
461
462                 int length = tokens.length;
463
464                 if (offset + segmentOffset < lineSegment.offset + length) {
465                     lineSegment.count = offset - (lineSegment.offset - segmentOffset);
466                     return x + Utilities.getTabbedTextWidth(lineSegment, fm, x, painter, 0);
467                 } else {
468                     lineSegment.count = length;
469                     x += Utilities.getTabbedTextWidth(lineSegment, fm, x, painter, 0);
470                     lineSegment.offset += length;
471                 }
472                 tokens = tokens.next;
473             }
474         }
475     }
476
477     /**
478      * Converts an x co-ordinate to an offset within a line.
479      * @param line The line
480      * @param x The x co-ordinate
481      */

482     public int xToOffset(int line, int x) {
483         TokenMarker tokenMarker = getTokenMarker();
484
485         /* Use painter's cached info for speed */
486         FontMetrics fm = painter.getFontMetrics();
487
488         getLineText(line, lineSegment);
489
490         char[] segmentArray = lineSegment.array;
491         int segmentOffset = lineSegment.offset;
492         int segmentCount = lineSegment.count;
493
494         int width = horizontalOffset;
495
496         if (tokenMarker == null) {
497             for (int i = 0; i < segmentCount; i++) {
498                 char c = segmentArray[i + segmentOffset];
499                 int charWidth;
500                 if (c == '\t')
501                     charWidth = (int) painter.nextTabStop(width, i) - width;
502                 else
503                     charWidth = fm.charWidth(c);
504
505                 if (painter.isBlockCaretEnabled()) {
506                     if (x - charWidth <= width)
507                         return i;
508                 } else {
509                     if (x - charWidth / 2 <= width)
510                         return i;
511                 }
512
513                 width += charWidth;
514             }
515
516             return segmentCount;
517         } else {
518             Token tokens;
519             if (painter.currentLineIndex == line && painter.currentLineTokens != null)
520                 tokens = painter.currentLineTokens;
521             else {
522                 painter.currentLineIndex = line;
523                 tokens = painter.currentLineTokens = tokenMarker.markTokens(lineSegment, line);
524             }
525
526             int offset = 0;
527             Toolkit toolkit = painter.getToolkit();
528             Font defaultFont = painter.getFont();
529             SyntaxStyle[] styles = painter.getStyles();
530
531             for (;;) {
532                 byte id = tokens.id;
533                 if (id == Token.END)
534                     return offset;
535
536                 if (id == Token.NULL)
537                     fm = painter.getFontMetrics();
538                 else
539                     fm = styles[id].getFontMetrics(defaultFont);
540
541                 int length = tokens.length;
542
543                 for (int i = 0; i < length; i++) {
544                     char c = segmentArray[segmentOffset + offset + i];
545                     int charWidth;
546                     if (c == '\t')
547                         charWidth = (int) painter.nextTabStop(width, offset + i) - width;
548                     else
549                         charWidth = fm.charWidth(c);
550
551                     if (painter.isBlockCaretEnabled()) {
552                         if (x - charWidth <= width)
553                             return offset + i;
554                     } else {
555                         if (x - charWidth / 2 <= width)
556                             return offset + i;
557                     }
558
559                     width += charWidth;
560                 }
561
562                 offset += length;
563                 tokens = tokens.next;
564             }
565         }
566     }
567
568     /**
569      * Converts a point to an offset, from the start of the text.
570      * @param x The x co-ordinate of the point
571      * @param y The y co-ordinate of the point
572      */

573     public int xyToOffset(int x, int y) {
574         int line = yToLine(y);
575         int start = getLineStartOffset(line);
576         return start + xToOffset(line, x);
577     }
578
579     /**
580      * Returns the document this text area is editing.
581      */

582     public final SyntaxDocument getDocument() {
583         return document;
584     }
585
586     /**
587      * Sets the document this text area is editing.
588      * @param document The document
589      */

590     public void setDocument(SyntaxDocument document) {
591         if (this.document == document)
592             return;
593         if (this.document != null)
594             this.document.removeDocumentListener(documentHandler);
595         this.document = document;
596
597         document.addDocumentListener(documentHandler);
598
599         select(0, 0);
600         updateScrollBars();
601         painter.repaint();
602     }
603
604     /**
605      * Returns the document's token marker. Equivalent to calling
606      * <code>getDocument().getTokenMarker()</code>.
607      */

608     public final TokenMarker getTokenMarker() {
609         return document.getTokenMarker();
610     }
611
612     /**
613      * Sets the document's token marker. Equivalent to caling
614      * <code>getDocument().setTokenMarker()</code>.
615      * @param tokenMarker The token marker
616      */

617     public final void setTokenMarker(TokenMarker tokenMarker) {
618         document.setTokenMarker(tokenMarker);
619     }
620
621     /**
622      * Returns the length of the document. Equivalent to calling
623      * <code>getDocument().getLength()</code>.
624      */

625     public final int getDocumentLength() {
626         return document.getLength();
627     }
628
629     /**
630      * Returns the number of lines in the document.
631      */

632     public final int getLineCount() {
633         return document.getDefaultRootElement().getElementCount();
634     }
635
636     /**
637      * Returns the line containing the specified offset.
638      * @param offset The offset
639      */

640     public final int getLineOfOffset(int offset) {
641         return document.getDefaultRootElement().getElementIndex(offset);
642     }
643
644     /**
645      * Returns the start offset of the specified line.
646      * @param line The line
647      * @return The start offset of the specified line, or -1 if the line is
648      * invalid
649      */

650     public int getLineStartOffset(int line) {
651         Element lineElement = document.getDefaultRootElement().getElement(line);
652         if (lineElement == null)
653             return -1;
654         else
655             return lineElement.getStartOffset();
656     }
657
658     /**
659      * Returns the end offset of the specified line.
660      * @param line The line
661      * @return The end offset of the specified line, or -1 if the line is
662      * invalid.
663      */

664     public int getLineEndOffset(int line) {
665         Element lineElement = document.getDefaultRootElement().getElement(line);
666         if (lineElement == null)
667             return -1;
668         else
669             return lineElement.getEndOffset();
670     }
671
672     /**
673      * Returns the length of the specified line.
674      * @param line The line
675      */

676     public int getLineLength(int line) {
677         Element lineElement = document.getDefaultRootElement().getElement(line);
678         if (lineElement == null)
679             return -1;
680         else
681             return lineElement.getEndOffset() - lineElement.getStartOffset() - 1;
682     }
683
684     /**
685      * Returns the entire text of this text area.
686      */

687     public String JavaDoc getText() {
688         try {
689             return document.getText(0, document.getLength());
690         } catch (BadLocationException bl) {
691             bl.printStackTrace();
692             return null;
693         }
694     }
695
696     /**
697      * Sets the entire text of this text area.
698      */

699     public void setText(String JavaDoc text) {
700         try {
701             document.beginCompoundEdit();
702             document.remove(0, document.getLength());
703             document.insertString(0, text, null);
704         } catch (BadLocationException bl) {
705             bl.printStackTrace();
706         } finally {
707             document.endCompoundEdit();
708         }
709     }
710
711     /**
712      * Returns the specified substring of the document.
713      * @param start The start offset
714      * @param len The length of the substring
715      * @return The substring, or null if the offsets are invalid
716      */

717     public final String JavaDoc getText(int start, int len) {
718         try {
719             return document.getText(start, len);
720         } catch (BadLocationException bl) {
721             bl.printStackTrace();
722             return null;
723         }
724     }
725
726     /**
727      * Copies the specified substring of the document into a segment.
728      * If the offsets are invalid, the segment will contain a null string.
729      * @param start The start offset
730      * @param len The length of the substring
731      * @param segment The segment
732      */

733     public final void getText(int start, int len, Segment segment) {
734         try {
735             document.getText(start, len, segment);
736         } catch (BadLocationException bl) {
737             bl.printStackTrace();
738             segment.offset = segment.count = 0;
739         }
740     }
741
742     /**
743      * Returns the text on the specified line.
744      * @param lineIndex The line
745      * @return The text, or null if the line is invalid
746      */

747     public final String JavaDoc getLineText(int lineIndex) {
748         int start = getLineStartOffset(lineIndex);
749         return getText(start, getLineEndOffset(lineIndex) - start - 1);
750     }
751
752     /**
753      * Copies the text on the specified line into a segment. If the line
754      * is invalid, the segment will contain a null string.
755      * @param lineIndex The line
756      */

757     public final void getLineText(int lineIndex, Segment segment) {
758         int start = getLineStartOffset(lineIndex);
759         getText(start, getLineEndOffset(lineIndex) - start - 1, segment);
760     }
761
762     /**
763      * Returns the selection start offset.
764      */

765     public final int getSelectionStart() {
766         return selectionStart;
767     }
768
769     /**
770      * Returns the offset where the selection starts on the specified
771      * line.
772      */

773     public int getSelectionStart(int line) {
774         if (line == selectionStartLine)
775             return selectionStart;
776         else if (rectSelect) {
777             Element map = document.getDefaultRootElement();
778             int start = selectionStart - map.getElement(selectionStartLine).getStartOffset();
779
780             Element lineElement = map.getElement(line);
781             int lineStart = lineElement.getStartOffset();
782             int lineEnd = lineElement.getEndOffset() - 1;
783             return Math.min(lineEnd, lineStart + start);
784         } else
785             return getLineStartOffset(line);
786     }
787
788     /**
789      * Returns the selection start line.
790      */

791     public final int getSelectionStartLine() {
792         return selectionStartLine;
793     }
794
795     /**
796      * Sets the selection start. The new selection will be the new
797      * selection start and the old selection end.
798      * @param selectionStart The selection start
799      * @see #select(int,int)
800      */

801     public final void setSelectionStart(int selectionStart) {
802         select(selectionStart, selectionEnd);
803     }
804
805     /**
806      * Returns the selection end offset.
807      */

808     public final int getSelectionEnd() {
809         return selectionEnd;
810     }
811
812     /**
813      * Returns the offset where the selection ends on the specified
814      * line.
815      */

816     public int getSelectionEnd(int line) {
817         if (line == selectionEndLine)
818             return selectionEnd;
819         else if (rectSelect) {
820             Element map = document.getDefaultRootElement();
821             int end = selectionEnd - map.getElement(selectionEndLine).getStartOffset();
822
823             Element lineElement = map.getElement(line);
824             int lineStart = lineElement.getStartOffset();
825             int lineEnd = lineElement.getEndOffset() - 1;
826             return Math.min(lineEnd, lineStart + end);
827         } else
828             return getLineEndOffset(line) - 1;
829     }
830
831     /**
832      * Returns the selection end line.
833      */

834     public final int getSelectionEndLine() {
835         return selectionEndLine;
836     }
837
838     /**
839      * Sets the selection end. The new selection will be the old
840      * selection start and the bew selection end.
841      * @param selectionEnd The selection end
842      * @see #select(int,int)
843      */

844     public final void setSelectionEnd(int selectionEnd) {
845         select(selectionStart, selectionEnd);
846     }
847
848     /**
849      * Returns the caret position. This will either be the selection
850      * start or the selection end, depending on which direction the
851      * selection was made in.
852      */

853     public final int getCaretPosition() {
854         return (biasLeft ? selectionStart : selectionEnd);
855     }
856
857     /**
858      * Returns the caret line.
859      */

860     public final int getCaretLine() {
861         return (biasLeft ? selectionStartLine : selectionEndLine);
862     }
863
864     /**
865      * Returns the mark position. This will be the opposite selection
866      * bound to the caret position.
867      * @see #getCaretPosition()
868      */

869     public final int getMarkPosition() {
870         return (biasLeft ? selectionEnd : selectionStart);
871     }
872
873     /**
874      * Returns the mark line.
875      */

876     public final int getMarkLine() {
877         return (biasLeft ? selectionEndLine : selectionStartLine);
878     }
879
880     /**
881      * Sets the caret position. The new selection will consist of the
882      * caret position only (hence no text will be selected)
883      * @param caret The caret position
884      * @see #select(int,int)
885      */

886     public final void setCaretPosition(int caret) {
887         select(caret, caret);
888     }
889
890     /**
891      * Selects all text in the document.
892      */

893     public final void selectAll() {
894         select(0, getDocumentLength());
895     }
896
897     /**
898      * Moves the mark to the caret position.
899      */

900     public final void selectNone() {
901         select(getCaretPosition(), getCaretPosition());
902     }
903
904     /**
905      * Selects from the start offset to the end offset. This is the
906      * general selection method used by all other selecting methods.
907      * The caret position will be start if start &lt; end, and end
908      * if end &gt; start.
909      * @param start The start offset
910      * @param end The end offset
911      */

912     public void select(int start, int end) {
913         int newStart, newEnd;
914         boolean newBias;
915         if (start <= end) {
916             newStart = start;
917             newEnd = end;
918             newBias = false;
919         } else {
920             newStart = end;
921             newEnd = start;
922             newBias = true;
923         }
924
925         if (newStart < 0 || newEnd > getDocumentLength()) {
926             throw new IllegalArgumentException JavaDoc(
927                 "Bounds out of" + " range: " + newStart + "," + newEnd);
928         }
929
930         // If the new position is the same as the old, we don't
931
// do all this crap, however we still do the stuff at
932
// the end (clearing magic position, scrolling)
933
if (newStart != selectionStart || newEnd != selectionEnd || newBias != biasLeft) {
934             int newStartLine = getLineOfOffset(newStart);
935             int newEndLine = getLineOfOffset(newEnd);
936
937             if (painter.isBracketHighlightEnabled()) {
938                 if (bracketLine != -1)
939                     painter.invalidateLine(bracketLine);
940                 updateBracketHighlight(end);
941                 if (bracketLine != -1)
942                     painter.invalidateLine(bracketLine);
943             }
944
945             painter.invalidateLineRange(selectionStartLine, selectionEndLine);
946             painter.invalidateLineRange(newStartLine, newEndLine);
947
948             document.addUndoableEdit(new CaretUndo(selectionStart, selectionEnd));
949
950             selectionStart = newStart;
951             selectionEnd = newEnd;
952             selectionStartLine = newStartLine;
953             selectionEndLine = newEndLine;
954             biasLeft = newBias;
955
956             fireCaretEvent();
957         }
958
959         // When the user is typing, etc, we don't want the caret
960
// to blink
961
blink = true;
962         caretTimer.restart();
963
964         // Disable rectangle select if selection start = selection end
965
if (selectionStart == selectionEnd)
966             rectSelect = false;
967
968         // Clear the `magic' caret position used by up/down
969
magicCaret = -1;
970
971         scrollToCaret();
972     }
973
974     /**
975      * Returns the selected text, or null if no selection is active.
976      */

977     public final String JavaDoc getSelectedText() {
978         if (selectionStart == selectionEnd)
979             return null;
980
981         if (rectSelect) {
982             // Return each row of the selection on a new line
983

984             Element map = document.getDefaultRootElement();
985
986             int start = selectionStart - map.getElement(selectionStartLine).getStartOffset();
987             int end = selectionEnd - map.getElement(selectionEndLine).getStartOffset();
988
989             // Certain rectangles satisfy this condition...
990
if (end < start) {
991                 int tmp = end;
992                 end = start;
993                 start = tmp;
994             }
995
996             StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
997             Segment seg = new Segment();
998
999             for (int i = selectionStartLine; i <= selectionEndLine; i++) {
1000                Element lineElement = map.getElement(i);
1001                int lineStart = lineElement.getStartOffset();
1002                int lineEnd = lineElement.getEndOffset() - 1;
1003                int lineLen = lineEnd - lineStart;
1004
1005                lineStart = Math.min(lineStart + start, lineEnd);
1006                lineLen = Math.min(end - start, lineEnd - lineStart);
1007
1008                getText(lineStart, lineLen, seg);
1009                buf.append(seg.array, seg.offset, seg.count);
1010
1011                if (i != selectionEndLine)
1012                    buf.append('\n');
1013            }
1014
1015            return buf.toString();
1016        } else {
1017            return getText(selectionStart, selectionEnd - selectionStart);
1018        }
1019    }
1020
1021    /**
1022     * Replaces the selection with the specified text.
1023     * @param selectedText The replacement text for the selection
1024     */

1025    public void setSelectedText(String JavaDoc selectedText) {
1026        if (!editable) {
1027            throw new InternalError JavaDoc("Text component" + " read only");
1028        }
1029
1030        document.beginCompoundEdit();
1031
1032        try {
1033            if (rectSelect) {
1034                Element map = document.getDefaultRootElement();
1035
1036                int start = selectionStart - map.getElement(selectionStartLine).getStartOffset();
1037                int end = selectionEnd - map.getElement(selectionEndLine).getStartOffset();
1038
1039                // Certain rectangles satisfy this condition...
1040
if (end < start) {
1041                    int tmp = end;
1042                    end = start;
1043                    start = tmp;
1044                }
1045
1046                int lastNewline = 0;
1047                int currNewline = 0;
1048
1049                for (int i = selectionStartLine; i <= selectionEndLine; i++) {
1050                    Element lineElement = map.getElement(i);
1051                    int lineStart = lineElement.getStartOffset();
1052                    int lineEnd = lineElement.getEndOffset() - 1;
1053                    int rectStart = Math.min(lineEnd, lineStart + start);
1054
1055                    document.remove(rectStart, Math.min(lineEnd - rectStart, end - start));
1056
1057                    if (selectedText == null)
1058                        continue;
1059
1060                    currNewline = selectedText.indexOf('\n', lastNewline);
1061                    if (currNewline == -1)
1062                        currNewline = selectedText.length();
1063
1064                    document.insertString(
1065                        rectStart,
1066                        selectedText.substring(lastNewline, currNewline),
1067                        null);
1068
1069                    lastNewline = Math.min(selectedText.length(), currNewline + 1);
1070                }
1071
1072                if (selectedText != null && currNewline != selectedText.length()) {
1073                    int offset = map.getElement(selectionEndLine).getEndOffset() - 1;
1074                    document.insertString(offset, "\n", null);
1075                    document.insertString(
1076                        offset + 1,
1077                        selectedText.substring(currNewline + 1),
1078                        null);
1079                }
1080            } else {
1081                document.remove(selectionStart, selectionEnd - selectionStart);
1082                if (selectedText != null) {
1083                    document.insertString(selectionStart, selectedText, null);
1084                }
1085            }
1086        } catch (BadLocationException bl) {
1087            bl.printStackTrace();
1088            throw new InternalError JavaDoc("Cannot replace" + " selection");
1089        }
1090        // No matter what happends... stops us from leaving document
1091
// in a bad state
1092
finally {
1093            document.endCompoundEdit();
1094        }
1095
1096        setCaretPosition(selectionEnd);
1097    }
1098
1099    /**
1100     * Returns true if this text area is editable, false otherwise.
1101     */

1102    public final boolean isEditable() {
1103        return editable;
1104    }
1105
1106    /**
1107     * Sets if this component is editable.
1108     * @param editable True if this text area should be editable,
1109     * false otherwise
1110     */

1111    public final void setEditable(boolean editable) {
1112        this.editable = editable;
1113    }
1114
1115    /**
1116     * Returns the right click popup menu.
1117     */

1118    public final JPopupMenu getRightClickPopup() {
1119        return popup;
1120    }
1121
1122    /**
1123     * Sets the right click popup menu.
1124     * @param popup The popup
1125     */

1126    public final void setRightClickPopup(JPopupMenu popup) {
1127        this.popup = popup;
1128    }
1129
1130    /**
1131     * Returns the `magic' caret position. This can be used to preserve
1132     * the column position when moving up and down lines.
1133     */

1134    public final int getMagicCaretPosition() {
1135        return magicCaret;
1136    }
1137
1138    /**
1139     * Sets the `magic' caret position. This can be used to preserve
1140     * the column position when moving up and down lines.
1141     * @param magicCaret The magic caret position
1142     */

1143    public final void setMagicCaretPosition(int magicCaret) {
1144        this.magicCaret = magicCaret;
1145    }
1146
1147    /**
1148     * Similar to <code>setSelectedText()</code>, but overstrikes the
1149     * appropriate number of characters if overwrite mode is enabled.
1150     * @param str The string
1151     * @see #setSelectedText(String)
1152     * @see #isOverwriteEnabled()
1153     */

1154    public void overwriteSetSelectedText(String JavaDoc str) {
1155        // Don't overstrike if there is a selection
1156
if (!overwrite || selectionStart != selectionEnd) {
1157            setSelectedText(str);
1158            return;
1159        }
1160
1161        // Don't overstrike if we're on the end of
1162
// the line
1163
int caret = getCaretPosition();
1164        int caretLineEnd = getLineEndOffset(getCaretLine());
1165        if (caretLineEnd - caret <= str.length()) {
1166            setSelectedText(str);
1167            return;
1168        }
1169
1170        document.beginCompoundEdit();
1171
1172        try {
1173            document.remove(caret, str.length());
1174            document.insertString(caret, str, null);
1175        } catch (BadLocationException bl) {
1176            bl.printStackTrace();
1177        } finally {
1178            document.endCompoundEdit();
1179        }
1180    }
1181
1182    /**
1183     * Returns true if overwrite mode is enabled, false otherwise.
1184     */

1185    public final boolean isOverwriteEnabled() {
1186        return overwrite;
1187    }
1188
1189    /**
1190     * Sets if overwrite mode should be enabled.
1191     * @param overwrite True if overwrite mode should be enabled,
1192     * false otherwise.
1193     */

1194    public final void setOverwriteEnabled(boolean overwrite) {
1195        this.overwrite = overwrite;
1196        painter.invalidateSelectedLines();
1197    }
1198
1199    /**
1200     * Returns true if the selection is rectangular, false otherwise.
1201     */

1202    public final boolean isSelectionRectangular() {
1203        return rectSelect;
1204    }
1205
1206    /**
1207     * Sets if the selection should be rectangular.
1208     * @param overwrite True if the selection should be rectangular,
1209     * false otherwise.
1210     */

1211    public final void setSelectionRectangular(boolean rectSelect) {
1212        this.rectSelect = rectSelect;
1213        painter.invalidateSelectedLines();
1214    }
1215
1216    /**
1217     * Returns the position of the highlighted bracket (the bracket
1218     * matching the one before the caret)
1219     */

1220    public final int getBracketPosition() {
1221        return bracketPosition;
1222    }
1223
1224    /**
1225     * Returns the line of the highlighted bracket (the bracket
1226     * matching the one before the caret)
1227     */

1228    public final int getBracketLine() {
1229        return bracketLine;
1230    }
1231
1232    /**
1233     * Adds a caret change listener to this text area.
1234     * @param listener The listener
1235     */

1236    public final void addCaretListener(CaretListener listener) {
1237        listenerList.add(CaretListener.class, listener);
1238    }
1239
1240    /**
1241     * Removes a caret change listener from this text area.
1242     * @param listener The listener
1243     */

1244    public final void removeCaretListener(CaretListener listener) {
1245        listenerList.remove(CaretListener.class, listener);
1246    }
1247
1248    /**
1249     * Deletes the selected text from the text area and places it
1250     * into the clipboard.
1251     */

1252    public void cut() {
1253        if (editable) {
1254            copy();
1255            setSelectedText("");
1256        }
1257    }
1258
1259    /**
1260     * Places the selected text into the clipboard.
1261     */

1262    public void copy() {
1263        if (selectionStart != selectionEnd) {
1264            Clipboard clipboard = getToolkit().getSystemClipboard();
1265
1266            String JavaDoc selection = getSelectedText();
1267
1268            int repeatCount = inputHandler.getRepeatCount();
1269            StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
1270            for (int i = 0; i < repeatCount; i++)
1271                buf.append(selection);
1272
1273            clipboard.setContents(new StringSelection(buf.toString()), null);
1274        }
1275    }
1276
1277    /**
1278     * Inserts the clipboard contents into the text.
1279     */

1280    public void paste() {
1281        if (editable) {
1282            Clipboard clipboard = getToolkit().getSystemClipboard();
1283            try {
1284                // The MacOS MRJ doesn't convert \r to \n,
1285
// so do it here
1286
String JavaDoc selection =
1287                    (
1288                        (String JavaDoc) clipboard.getContents(this).getTransferData(
1289                            DataFlavor.stringFlavor)).replace(
1290                        '\r',
1291                        '\n');
1292
1293                int repeatCount = inputHandler.getRepeatCount();
1294                StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
1295                for (int i = 0; i < repeatCount; i++)
1296                    buf.append(selection);
1297                selection = buf.toString();
1298                setSelectedText(selection);
1299            } catch (Exception JavaDoc e) {
1300                getToolkit().beep();
1301                System.err.println("Clipboard does not" + " contain a string");
1302            }
1303        }
1304    }
1305
1306    /**
1307     * Called by the AWT when this component is removed from it's parent.
1308     * This stops clears the currently focused component.
1309     */

1310    public void removeNotify() {
1311        super.removeNotify();
1312        if (focusedComponent == this)
1313            focusedComponent = null;
1314    }
1315
1316    /**
1317     * Forwards key events directly to the input handler.
1318     * This is slightly faster than using a KeyListener
1319     * because some Swing overhead is avoided.
1320     */

1321    public void processKeyEvent(KeyEvent evt) {
1322        int keyCode = evt.getKeyCode();
1323        if (keyCode != KeyEvent.VK_TAB)
1324            getParent().dispatchEvent(evt);
1325
1326        if (inputHandler == null)
1327            return;
1328
1329        switch (evt.getID()) {
1330            case KeyEvent.KEY_TYPED :
1331                inputHandler.keyTyped(evt);
1332                break;
1333            case KeyEvent.KEY_PRESSED :
1334                inputHandler.keyPressed(evt);
1335                break;
1336            case KeyEvent.KEY_RELEASED :
1337                inputHandler.keyReleased(evt);
1338                break;
1339        }
1340    }
1341
1342    // protected members
1343
protected static String JavaDoc CENTER = "center";
1344    protected static String JavaDoc RIGHT = "right";
1345    protected static String JavaDoc BOTTOM = "bottom";
1346
1347    protected static JEditTextArea focusedComponent;
1348    protected static Timer caretTimer;
1349
1350    protected TextAreaPainter painter;
1351
1352    protected JPopupMenu popup;
1353
1354    protected EventListenerList listenerList;
1355    protected MutableCaretEvent caretEvent;
1356
1357    protected boolean caretBlinks;
1358    protected boolean caretVisible;
1359    protected boolean blink;
1360
1361    protected boolean editable;
1362
1363    protected int firstLine;
1364    protected int visibleLines;
1365    protected int electricScroll;
1366
1367    protected int horizontalOffset;
1368
1369    protected JScrollBar vertical;
1370    protected JScrollBar horizontal;
1371    protected boolean scrollBarsInitialized;
1372
1373    protected InputHandler inputHandler;
1374    protected SyntaxDocument document;
1375    protected DocumentHandler documentHandler;
1376
1377    protected Segment lineSegment;
1378
1379    protected int selectionStart;
1380    protected int selectionStartLine;
1381    protected int selectionEnd;
1382    protected int selectionEndLine;
1383    protected boolean biasLeft;
1384
1385    protected int bracketPosition;
1386    protected int bracketLine;
1387
1388    protected int magicCaret;
1389    protected boolean overwrite;
1390    protected boolean rectSelect;
1391
1392    protected void fireCaretEvent() {
1393        Object JavaDoc[] listeners = listenerList.getListenerList();
1394        for (int i = listeners.length - 2; i >= 0; i--) {
1395            if (listeners[i] == CaretListener.class) {
1396                ((CaretListener) listeners[i + 1]).caretUpdate(caretEvent);
1397            }
1398        }
1399    }
1400
1401    protected void updateBracketHighlight(int newCaretPosition) {
1402        if (newCaretPosition == 0) {
1403            bracketPosition = bracketLine = -1;
1404            return;
1405        }
1406
1407        try {
1408            int offset = TextUtilities.findMatchingBracket(document, newCaretPosition - 1);
1409            if (offset != -1) {
1410                bracketLine = getLineOfOffset(offset);
1411                bracketPosition = offset - getLineStartOffset(bracketLine);
1412                return;
1413            }
1414        } catch (BadLocationException bl) {
1415            bl.printStackTrace();
1416        }
1417
1418        bracketLine = bracketPosition = -1;
1419    }
1420
1421    protected void documentChanged(DocumentEvent evt) {
1422        DocumentEvent.ElementChange ch = evt.getChange(document.getDefaultRootElement());
1423
1424        int count;
1425        if (ch == null)
1426            count = 0;
1427        else
1428            count = ch.getChildrenAdded().length - ch.getChildrenRemoved().length;
1429
1430        int line = getLineOfOffset(evt.getOffset());
1431        if (count == 0) {
1432            painter.invalidateLine(line);
1433        }
1434        // do magic stuff
1435
else if (line < firstLine) {
1436            setFirstLine(firstLine + count);
1437        }
1438        // end of magic stuff
1439
else {
1440            painter.invalidateLineRange(line, firstLine + visibleLines);
1441            updateScrollBars();
1442        }
1443    }
1444
1445    class ScrollLayout implements LayoutManager {
1446        public void addLayoutComponent(String JavaDoc name, Component comp) {
1447            if (name.equals(CENTER))
1448                center = comp;
1449            else if (name.equals(RIGHT))
1450                right = comp;
1451            else if (name.equals(BOTTOM))
1452                bottom = comp;
1453            else if (name.equals(LEFT_OF_SCROLLBAR))
1454                leftOfScrollBar.addElement(comp);
1455        }
1456
1457        public void removeLayoutComponent(Component comp) {
1458            if (center == comp)
1459                center = null;
1460            if (right == comp)
1461                right = null;
1462            if (bottom == comp)
1463                bottom = null;
1464            else
1465                leftOfScrollBar.removeElement(comp);
1466        }
1467
1468        public Dimension preferredLayoutSize(Container parent) {
1469            Dimension dim = new Dimension();
1470            Insets insets = getInsets();
1471            dim.width = insets.left + insets.right;
1472            dim.height = insets.top + insets.bottom;
1473
1474            Dimension centerPref = center.getPreferredSize();
1475            dim.width += centerPref.width;
1476            dim.height += centerPref.height;
1477            Dimension rightPref = right.getPreferredSize();
1478            dim.width += rightPref.width;
1479            Dimension bottomPref = bottom.getPreferredSize();
1480            dim.height += bottomPref.height;
1481
1482            return dim;
1483        }
1484
1485        public Dimension minimumLayoutSize(Container parent) {
1486            Dimension dim = new Dimension();
1487            Insets insets = getInsets();
1488            dim.width = insets.left + insets.right;
1489            dim.height = insets.top + insets.bottom;
1490
1491            Dimension centerPref = center.getMinimumSize();
1492            dim.width += centerPref.width;
1493            dim.height += centerPref.height;
1494            Dimension rightPref = right.getMinimumSize();
1495            dim.width += rightPref.width;
1496            Dimension bottomPref = bottom.getMinimumSize();
1497            dim.height += bottomPref.height;
1498
1499            return dim;
1500        }
1501
1502        public void layoutContainer(Container parent) {
1503            Dimension size = parent.getSize();
1504            Insets insets = parent.getInsets();
1505            int itop = insets.top;
1506            int ileft = insets.left;
1507            int ibottom = insets.bottom;
1508            int iright = insets.right;
1509
1510            int rightWidth = right.getPreferredSize().width;
1511            int bottomHeight = bottom.getPreferredSize().height;
1512            int centerWidth = size.width - rightWidth - ileft - iright;
1513            int centerHeight = size.height - bottomHeight - itop - ibottom;
1514
1515            center.setBounds(ileft, itop, centerWidth, centerHeight);
1516
1517            right.setBounds(ileft + centerWidth, itop, rightWidth, centerHeight);
1518
1519            // Lay out all status components, in order
1520
Enumeration JavaDoc status = leftOfScrollBar.elements();
1521            while (status.hasMoreElements()) {
1522                Component comp = (Component) status.nextElement();
1523                Dimension dim = comp.getPreferredSize();
1524                comp.setBounds(ileft, itop + centerHeight, dim.width, bottomHeight);
1525                ileft += dim.width;
1526            }
1527
1528            bottom.setBounds(
1529                ileft,
1530                itop + centerHeight,
1531                size.width - rightWidth - ileft - iright,
1532                bottomHeight);
1533        }
1534
1535        // private members
1536
private Component center;
1537        private Component right;
1538        private Component bottom;
1539        private Vector JavaDoc leftOfScrollBar = new Vector JavaDoc();
1540    }
1541
1542    static class CaretBlinker implements ActionListener {
1543        public void actionPerformed(ActionEvent evt) {
1544            if (focusedComponent != null && focusedComponent.hasFocus())
1545                focusedComponent.blinkCaret();
1546        }
1547    }
1548
1549    class MutableCaretEvent extends CaretEvent {
1550        MutableCaretEvent() {
1551            super(JEditTextArea.this);
1552        }
1553
1554        public int getDot() {
1555            return getCaretPosition();
1556        }
1557
1558        public int getMark() {
1559            return getMarkPosition();
1560        }
1561    }
1562
1563    class AdjustHandler implements AdjustmentListener {
1564        public void adjustmentValueChanged(final AdjustmentEvent evt) {
1565            if (!scrollBarsInitialized)
1566                return;
1567
1568            // If this is not done, mousePressed events accumilate
1569
// and the result is that scrolling doesn't stop after
1570
// the mouse is released
1571
SwingUtilities.invokeLater(new Runnable JavaDoc() {
1572                public void run() {
1573                    if (evt.getAdjustable() == vertical)
1574                        setFirstLine(vertical.getValue());
1575                    else
1576                        setHorizontalOffset(-horizontal.getValue());
1577                }
1578            });
1579        }
1580    }
1581
1582    class ComponentHandler extends ComponentAdapter {
1583        public void componentResized(ComponentEvent evt) {
1584            recalculateVisibleLines();
1585            scrollBarsInitialized = true;
1586        }
1587    }
1588
1589    class DocumentHandler implements DocumentListener {
1590        public void insertUpdate(DocumentEvent evt) {
1591            documentChanged(evt);
1592
1593            int offset = evt.getOffset();
1594            int length = evt.getLength();
1595
1596            int newStart;
1597            int newEnd;
1598
1599            if (selectionStart > offset
1600                || (selectionStart == selectionEnd && selectionStart == offset))
1601                newStart = selectionStart + length;
1602            else
1603                newStart = selectionStart;
1604
1605            if (selectionEnd >= offset)
1606                newEnd = selectionEnd + length;
1607            else
1608                newEnd = selectionEnd;
1609
1610            select(newStart, newEnd);
1611        }
1612
1613        public void removeUpdate(DocumentEvent evt) {
1614            documentChanged(evt);
1615
1616            int offset = evt.getOffset();
1617            int length = evt.getLength();
1618
1619            int newStart;
1620            int newEnd;
1621
1622            if (selectionStart > offset) {
1623                if (selectionStart > offset + length)
1624                    newStart = selectionStart - length;
1625                else
1626                    newStart = offset;
1627            } else
1628                newStart = selectionStart;
1629
1630            if (selectionEnd > offset) {
1631                if (selectionEnd > offset + length)
1632                    newEnd = selectionEnd - length;
1633                else
1634                    newEnd = offset;
1635            } else
1636                newEnd = selectionEnd;
1637
1638            select(newStart, newEnd);
1639        }
1640
1641        public void changedUpdate(DocumentEvent evt) {
1642        }
1643    }
1644
1645    class DragHandler implements MouseMotionListener {
1646        public void mouseDragged(MouseEvent evt) {
1647            if (popup != null && popup.isVisible())
1648                return;
1649
1650            setSelectionRectangular((evt.getModifiers() & InputEvent.CTRL_MASK) != 0);
1651            select(getMarkPosition(), xyToOffset(evt.getX(), evt.getY()));
1652        }
1653
1654        public void mouseMoved(MouseEvent evt) {
1655        }
1656    }
1657
1658    class FocusHandler implements FocusListener {
1659        public void focusGained(FocusEvent evt) {
1660            setCaretVisible(true);
1661            focusedComponent = JEditTextArea.this;
1662        }
1663
1664        public void focusLost(FocusEvent evt) {
1665            setCaretVisible(false);
1666            focusedComponent = null;
1667        }
1668    }
1669
1670    class MouseHandler extends MouseAdapter {
1671        public void mousePressed(MouseEvent evt) {
1672            requestFocus();
1673
1674            // Focus events not fired sometimes?
1675
setCaretVisible(true);
1676            focusedComponent = JEditTextArea.this;
1677
1678            if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) != 0 && popup != null) {
1679                popup.show(painter, evt.getX(), evt.getY());
1680                return;
1681            }
1682
1683            int line = yToLine(evt.getY());
1684            int offset = xToOffset(line, evt.getX());
1685            int dot = getLineStartOffset(line) + offset;
1686
1687            switch (evt.getClickCount()) {
1688                case 1 :
1689                    doSingleClick(evt, line, offset, dot);
1690                    break;
1691                case 2 :
1692                    // It uses the bracket matching stuff, so
1693
// it can throw a BLE
1694
try {
1695                        doDoubleClick(evt, line, offset, dot);
1696                    } catch (BadLocationException bl) {
1697                        bl.printStackTrace();
1698                    }
1699                    break;
1700                case 3 :
1701                    doTripleClick(evt, line, offset, dot);
1702                    break;
1703            }
1704        }
1705
1706        private void doSingleClick(MouseEvent evt, int line, int offset, int dot) {
1707            if ((evt.getModifiers() & InputEvent.SHIFT_MASK) != 0) {
1708                rectSelect = (evt.getModifiers() & InputEvent.CTRL_MASK) != 0;
1709                select(getMarkPosition(), dot);
1710            } else
1711                setCaretPosition(dot);
1712        }
1713
1714        private void doDoubleClick(MouseEvent evt, int line, int offset, int dot)
1715            throws BadLocationException {
1716            // Ignore empty lines
1717
if (getLineLength(line) == 0)
1718                return;
1719
1720            try {
1721                int bracket = TextUtilities.findMatchingBracket(document, Math.max(0, dot - 1));
1722                if (bracket != -1) {
1723                    int mark = getMarkPosition();
1724                    // Hack
1725
if (bracket > mark) {
1726                        bracket++;
1727                        mark--;
1728                    }
1729                    select(mark, bracket);
1730                    return;
1731                }
1732            } catch (BadLocationException bl) {
1733                bl.printStackTrace();
1734            }
1735
1736            // Ok, it's not a bracket... select the word
1737
String JavaDoc lineText = getLineText(line);
1738            char ch = lineText.charAt(Math.max(0, offset - 1));
1739
1740            String JavaDoc noWordSep = (String JavaDoc) document.getProperty("noWordSep");
1741            if (noWordSep == null)
1742                noWordSep = "";
1743
1744            // If the user clicked on a non-letter char,
1745
// we select the surrounding non-letters
1746
boolean selectNoLetter =
1747                (!Character.isLetterOrDigit(ch) && noWordSep.indexOf(ch) == -1);
1748
1749            int wordStart = 0;
1750
1751            for (int i = offset - 1; i >= 0; i--) {
1752                ch = lineText.charAt(i);
1753                if (selectNoLetter
1754                    ^ (!Character.isLetterOrDigit(ch) && noWordSep.indexOf(ch) == -1)) {
1755                    wordStart = i + 1;
1756                    break;
1757                }
1758            }
1759
1760            int wordEnd = lineText.length();
1761            for (int i = offset; i < lineText.length(); i++) {
1762                ch = lineText.charAt(i);
1763                if (selectNoLetter
1764                    ^ (!Character.isLetterOrDigit(ch) && noWordSep.indexOf(ch) == -1)) {
1765                    wordEnd = i;
1766                    break;
1767                }
1768            }
1769
1770            int lineStart = getLineStartOffset(line);
1771            select(lineStart + wordStart, lineStart + wordEnd);
1772
1773            /*
1774            String lineText = getLineText(line);
1775            String noWordSep = (String)document.getProperty("noWordSep");
1776            int wordStart = TextUtilities.findWordStart(lineText,offset,noWordSep);
1777            int wordEnd = TextUtilities.findWordEnd(lineText,offset,noWordSep);
1778            
1779            int lineStart = getLineStartOffset(line);
1780            select(lineStart + wordStart,lineStart + wordEnd);
1781            */

1782        }
1783
1784        private void doTripleClick(MouseEvent evt, int line, int offset, int dot) {
1785            select(getLineStartOffset(line), getLineEndOffset(line) - 1);
1786        }
1787    }
1788
1789    class CaretUndo extends AbstractUndoableEdit {
1790        private int start;
1791        private int end;
1792
1793        CaretUndo(int start, int end) {
1794            this.start = start;
1795            this.end = end;
1796        }
1797
1798        public boolean isSignificant() {
1799            return false;
1800        }
1801
1802        public String JavaDoc getPresentationName() {
1803            return "caret move";
1804        }
1805
1806        public void undo() throws CannotUndoException {
1807            super.undo();
1808
1809            select(start, end);
1810        }
1811
1812        public void redo() throws CannotRedoException {
1813            super.redo();
1814
1815            select(start, end);
1816        }
1817
1818        public boolean addEdit(UndoableEdit edit) {
1819            if (edit instanceof CaretUndo) {
1820                CaretUndo cedit = (CaretUndo) edit;
1821                start = cedit.start;
1822                end = cedit.end;
1823                cedit.die();
1824
1825                return true;
1826            } else
1827                return false;
1828        }
1829    }
1830
1831    class ScrollWheelHandler implements MouseWheelListener {
1832        /**
1833        * Invoked when the mouse wheel is rotated.
1834        * @see MouseWheelEvent
1835        */

1836        public void mouseWheelMoved(MouseWheelEvent e) {
1837            int line_to_scroll_to;
1838            int units = e.getUnitsToScroll();
1839
1840            line_to_scroll_to = firstLine + units;
1841            if (units < 0) {
1842                line_to_scroll_to += electricScroll;
1843                if (line_to_scroll_to < 0) {
1844                    line_to_scroll_to = 0;
1845                }
1846            } else if (units > 0) {
1847                line_to_scroll_to += visibleLines - 1 - electricScroll;
1848                if (line_to_scroll_to >= getLineCount()) {
1849                    line_to_scroll_to = getLineCount() - 1;
1850                }
1851            }
1852
1853            scrollTo(line_to_scroll_to, 0);
1854        }
1855    }
1856
1857    static {
1858        caretTimer = new Timer(500, new CaretBlinker());
1859        caretTimer.setInitialDelay(500);
1860        caretTimer.start();
1861    }
1862}
1863
Popular Tags