Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: jsource/gui/JSEditor.java


1   package jsource.gui;
2   
3   
4   /**
5    * JSEditor.java 03/31/03
6    *
7    * This program is free software; you can redistribute it and/or modify
8    * it under the terms of the GNU Library General Public License as published
9    * by the Free Software Foundation; either version 2 of the License, or
10   * (at your option) any later version.
11   *
12   * This program is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU Library General Public License for more details.
16   */
17  import javax.swing.border.*;
18  import javax.swing.event.*;
19  import javax.swing.text.*;
20  import javax.swing.undo.*;
21  import javax.swing.*;
22  import java.awt.datatransfer.*;
23  import java.awt.event.*;
24  import java.awt.*;
25  import java.util.Enumeration;
26  import java.util.Vector;
27  import java.util.HashSet;
28  import jsource.syntax.*;
29  import jsource.syntax.tokenmarker.*;
30  import jsource.util.*;
31  
32  
33  /**
34   * <code>JSEditor</code> is JSource's text editing component. It is more suited for editing program
35   * source code than JEditorPane, because it drops the unnecessary features (images, variable-width lines,
36   * and so on) and adds a whole bunch of useful goodies such as:
37   * <ul>
38   * <li>More flexible key binding scheme
39   * <li>Supports macro recorders
40   * <li>Rectangular selection
41   * <li>Bracket highlighting
42   * <li>Syntax highlighting
43   * <li>Command repetition
44   * <li>Block caret can be enabled
45   * </ul>
46   *
47   * Based on jEdit's JEditTextArea component by Slava Pestov.
48   *
49   * @author Panagiotis Plevrakis
50   * <br>Email: pplevrakis@hotmail.com
51   * <br>URL:   http://jsource.sourceforge.net
52   */
53  public class JSEditor extends JComponent {
54  
55      private static String CENTER = "center";
56      private static String RIGHT = "right";
57      private static String BOTTOM = "bottom";
58      private static String LEFT = "left";
59  
60      private static JSEditor focusedComponent = null;
61      private static Timer caretTimer = null;
62      private static Timer refreshTimer = null;
63  
64      private TextAreaPainter painter = null;
65      private MainFrame main = null;
66  
67      private JPopupMenu popup = null;
68  
69      private EventListenerList listenerList = null;
70      private MutableCaretEvent caretEvent = null;
71  
72      private Gutter mGutter = null;
73      private HashSet mBreakPts = new HashSet();
74  
75      private boolean caretBlinks = false;
76      private boolean caretVisible = false;
77      private boolean blink = false;
78  
79      private boolean editable = false;
80  
81      private int firstLine = 0;
82      private int visibleLines = 0;
83      private int electricScroll = 0;
84  
85      private int horizontalOffset = 0;
86  
87      private JScrollBar vertical = null;
88      private JScrollBar horizontal = null;
89      private boolean scrollBarsInitialized;
90  
91      private InputHandler inputHandler = null;
92      private SyntaxDocument document = null;
93      private DocumentHandler documentHandler = null;
94  
95      private Segment lineSegment = null;
96  
97      private int selectionStart = 0;
98      private int selectionStartLine = 0;
99      private int selectionEnd = 0;
100     private int selectionEndLine = 0;
101     private boolean biasLeft = false;
102 
103     private int bracketPosition = 0;
104     private int bracketLine = 0;
105 
106     private int magicCaret = 0;
107     private boolean overwrite = false;
108     private boolean rectSelect = false;
109 
110     public boolean modifiedSinceSave = false;
111 
112     private boolean indent = false;
113     private String tab = "    ";
114     private String lineSep = JSConstants.LINE_SEP;
115 
116     /**
117      * Creates a new JSEditor with the default settings.
118      */
119     public JSEditor(MainFrame main) {
120         this(TextAreaDefaults.getDefaults(), main);
121         this.main = main;
122     }
123 
124     /**
125      * Creates a new JSEditor with the specified settings.
126      * @param defaults The default settings
127      */
128     public JSEditor(TextAreaDefaults defaults, MainFrame main) {
129         // Enable the necessary events
130         enableEvents(AWTEvent.KEY_EVENT_MASK);
131 
132         // Initialize some misc. stuff
133         painter = new TextAreaPainter(this, defaults);
134         documentHandler = new DocumentHandler();
135         listenerList = new EventListenerList();
136         caretEvent = new MutableCaretEvent();
137         lineSegment = new Segment();
138         bracketLine = bracketPosition = -1;
139         blink = true;
140         mGutter = new Gutter(this);
141 
142         // Initialize the GUI
143         setLayout(new ScrollLayout());
144         add(LEFT, mGutter);
145         add(CENTER, painter);
146         add(RIGHT, vertical = new JScrollBar(JScrollBar.VERTICAL));
147         add(BOTTOM, horizontal = new JScrollBar(JScrollBar.HORIZONTAL));
148 
149         // Add some event listeners
150         vertical.addAdjustmentListener(new AdjustHandler());
151         horizontal.addAdjustmentListener(new AdjustHandler());
152         painter.addComponentListener(new ComponentHandler());
153         painter.addMouseListener(new MouseHandler());
154         painter.addMouseMotionListener(new DragHandler());
155         addFocusListener(new FocusHandler());
156 
157         // Setup the input handler
158         InputHandler inputHandler = new DefaultInputHandler(main);
159         inputHandler.addDefaultKeyBindings();
160         setInputHandler(inputHandler);
161 
162     // Load the defaults
163         setDocument(defaults.document);
164         editable = defaults.editable;
165         caretVisible = defaults.caretVisible;
166         caretBlinks = defaults.caretBlinks;
167         electricScroll = defaults.electricScroll;
168 
169         // We don't seem to get the initial focus event?
170         focusedComponent = this;
171     }
172 
173     /**
174      * Overrides <code>update</code> for smoother repainting.
175      * Implemented 01/02/03
176      * @param g the <code>Graphics</code> context
177      */
178     public void update(Graphics g) {
179         paint(g);
180     }
181 
182     /**
183      * Returns the entire text of this text area.
184      */
185     public String getText() {
186         try {
187             return document.getText(0, document.getLength());
188         } catch (BadLocationException bl) {
189             return null;
190         }
191     }
192 
193     /**
194      * Sets the entire text of this text area.
195      */
196     public void setText(String text) {
197 
198         try {
199             document.beginCompoundEdit();
200             document.remove(0, document.getLength());
201             document.insertString(0, text, null);
202             if(!modifiedSinceSave) {
203                 modifiedSinceSave = true;
204         }
205         } catch (BadLocationException bl) {
206         } finally {
207             document.endCompoundEdit();
208         }
209     }
210 
211     /**
212      * Appends the given text to the end of the document.
213      *
214      * @param str the text to append
215      */
216     public void append(String str) {
217         Document doc = getDocument();
218 
219         if (doc != null) {
220             try {
221                 doc.insertString(doc.getLength(), str, null);
222                 if(!modifiedSinceSave) {
223                     modifiedSinceSave = true;
224         }
225             } catch (BadLocationException bl) {
226 
227       }
228         }
229     }
230 
231     /**
232      * Inserts the given text after the current caret position.
233      *
234      * @param str the text to insert
235      */
236     public void insert(String str) {
237         Document doc = getDocument();
238 
239         if (doc != null) {
240             try {
241                 doc.insertString(getCaretPosition(), str, null);
242                 if(!modifiedSinceSave) {
243                     modifiedSinceSave = true;
244         }
245             } catch (BadLocationException bl) {
246 
247       }
248         }
249     }
250 
251     /**
252      * Returns the object responsible for painting this text area.
253      */
254     public final TextAreaPainter getPainter() {
255         return painter;
256     }
257 
258     /**
259      * Returns the input handler.
260      */
261     public final InputHandler getInputHandler() {
262         return inputHandler;
263     }
264 
265     /**
266      * Sets the input handler.
267      * @param inputHandler The new input handler
268      */
269     public void setInputHandler(InputHandler inputHandler) {
270         this.inputHandler = inputHandler;
271     }
272 
273     /**
274      * Returns true if the caret is blinking, false otherwise.
275      */
276     public final boolean isCaretBlinkEnabled() {
277         return caretBlinks;
278     }
279 
280     /**
281      * Toggles caret blinking.
282      * @param caretBlinks True if the caret should blink, false otherwise
283      */
284     public void setCaretBlinkEnabled(boolean caretBlinks) {
285         this.caretBlinks = caretBlinks;
286         if (!caretBlinks)
287             blink = false;
288 
289         painter.invalidateSelectedLines();
290     }
291 
292     /**
293      * Returns true if the caret is visible, false otherwise.
294      */
295     public final boolean isCaretVisible() {
296         return (!caretBlinks || blink) && caretVisible;
297     }
298 
299     /**
300      * Sets if the caret should be visible.
301      * @param caretVisible true if the caret should be visible, false otherwise
302      */
303     public void setCaretVisible(boolean caretVisible) {
304         this.caretVisible = caretVisible;
305         blink = true;
306 
307         painter.invalidateSelectedLines();
308     }
309 
310     /**
311      * Blinks the caret.
312      */
313     public final void blinkCaret() {
314         if (caretBlinks) {
315             blink = !blink;
316             painter.invalidateSelectedLines();
317         } else
318             blink = true;
319     }
320 
321     /**
322      * Returns the number of lines from the top and bottom of the
323      * text area that are always visible.
324      */
325     public final int getElectricScroll() {
326         return electricScroll;
327     }
328 
329     /**
330      * Sets the number of lines from the top and bottom of the text
331      * area that are always visible
332      * @param electricScroll the number of lines always visible from
333      * the top or bottom
334      */
335     public final void setElectricScroll(int electricScroll) {
336         this.electricScroll = electricScroll;
337     }
338 
339     /**
340      * Updates the state of the scroll bars. This should be called
341      * if the number of lines in the document changes, or when the
342      * size of the text area changes.
343      */
344     public void updateScrollBars() {
345         if (vertical != null && visibleLines != 0) {
346             vertical.setValues(firstLine, visibleLines, 0, getLineCount());
347             vertical.setUnitIncrement(2);
348             vertical.setBlockIncrement(visibleLines);
349         }
350 
351         int width = painter.getWidth();
352 
353         if (horizontal != null && width != 0) {
354             horizontal.setValues(-horizontalOffset, width, 0, width * 5);
355             horizontal.setUnitIncrement(painter.getFontMetrics().charWidth('w'));
356             horizontal.setBlockIncrement(width / 2);
357         }
358     }
359 
360     /**
361      * Returns the line displayed at the text area's origin.
362      */
363     public final int getFirstLine() {
364         return firstLine;
365     }
366 
367     /**
368      * Sets the line displayed at the text area's origin without
369      * updating the scroll bars.
370      */
371     public void setFirstLine(int firstLine) {
372         if (firstLine == this.firstLine)
373             return;
374         int oldFirstLine = this.firstLine;
375 
376         this.firstLine = firstLine - 1;
377         if (firstLine != vertical.getValue())
378             updateScrollBars();
379         painter.repaint();
380         mGutter.repaint();
381     }
382 
383     /**
384      * Returns the number of lines visible in this text area.
385      */
386     public final int getVisibleLines() {
387         return visibleLines;
388     }
389 
390     /**
391      * Recalculates the number of visible lines. This should not
392      * be called directly.
393      */
394     public final void recalculateVisibleLines() {
395         if (painter == null)
396             return;
397         int height = painter.getHeight();
398         int lineHeight = painter.getFontMetrics().getHeight();
399         int oldVisibleLines = visibleLines;
400 
401         visibleLines = height / lineHeight;
402         updateScrollBars();
403     }
404 
405     /**
406      * Returns the horizontal offset of drawn lines.
407      */
408     public final int getHorizontalOffset() {
409         return horizontalOffset;
410     }
411 
412     /**
413      * Sets the horizontal offset of drawn lines. This can be used to
414      * implement horizontal scrolling.
415      * @param horizontalOffset offset the new horizontal offset
416      */
417     public void setHorizontalOffset(int horizontalOffset) {
418         if (horizontalOffset == this.horizontalOffset)
419             return;
420         this.horizontalOffset = horizontalOffset;
421         if (horizontalOffset != horizontal.getValue())
422             updateScrollBars();
423         painter.repaint();
424     }
425 
426     /**
427      * A fast way of changing both the first line and horizontal
428      * offset.
429      * @param firstLine the new first line
430      * @param horizontalOffset the new horizontal offset
431      * @return true if any of the values were changed, false otherwise
432      */
433     public boolean setOrigin(int firstLine, int horizontalOffset) {
434         boolean changed = false;
435         int oldFirstLine = this.firstLine;
436 
437         if (horizontalOffset != this.horizontalOffset) {
438             this.horizontalOffset = horizontalOffset;
439             changed = true;
440         }
441 
442         if (firstLine != this.firstLine) {
443             this.firstLine = firstLine;
444             changed = true;
445         }
446 
447         if (changed) {
448             updateScrollBars();
449             painter.repaint();
450             mGutter.repaint();
451         }
452 
453         return changed;
454     }
455 
456     /**
457      * Ensures that the caret is visible by scrolling the text area if necessary.
458      * @return true if scrolling was actually performed, false if the
459      * caret was already visible
460      */
461     public boolean scrollToCaret() {
462         int line = getCaretLine();
463         int lineStart = getLineStartOffset(line);
464         int offset = Math.max(0, Math.min(getLineLength(line) - 1,
465                 getCaretPosition() - lineStart));
466 
467         return scrollTo(line, offset);
468     }
469 
470     /**
471      * Ensures that the specified line and offset is visible by scrolling
472      * the text area if necessary.
473      * @param line the line to scroll to
474      * @param offset the offset in the line to scroll to
475      * @return true if scrolling was actually performed, false if the
476      * line and offset was already visible
477      */
478     public boolean scrollTo(int line, int offset) {
479         // visibleLines == 0 before the component is realized
480         // we can't do any proper scrolling then, so I have this hack...
481         if (visibleLines == 0) {
482             setFirstLine(Math.max(0, line - electricScroll));
483             return true;
484         }
485 
486         int newFirstLine = firstLine;
487         int newHorizontalOffset = horizontalOffset;
488 
489         if (line < firstLine + electricScroll) {
490             newFirstLine = Math.max(0, line - electricScroll);
491         } else if (line + electricScroll >= firstLine + visibleLines) {
492             newFirstLine = (line - visibleLines) + electricScroll + 1;
493             if (newFirstLine + visibleLines >= getLineCount())
494                 newFirstLine = getLineCount() - visibleLines;
495             if (newFirstLine < 0)
496                 newFirstLine = 0;
497         }
498 
499         int x = _offsetToX(line, offset);
500         int width = painter.getFontMetrics().charWidth('w');
501 
502         if (x < 0) {
503             newHorizontalOffset = Math.min(0, horizontalOffset - x + width + 5);
504         } else if (x + width >= painter.getWidth()) {
505             newHorizontalOffset = horizontalOffset + (painter.getWidth() - x)
506                     - width - 5;
507         }
508 
509         return setOrigin(newFirstLine, newHorizontalOffset);
510     }
511 
512     /**
513      * Converts a line index to a y co-ordinate.
514      * @param line the line
515      */
516     public int lineToY(int line) {
517         FontMetrics fm = painter.getFontMetrics();
518 
519         return (line - firstLine) * fm.getHeight()
520                 - (fm.getLeading() + fm.getMaxDescent());
521     }
522 
523     /**
524      * Converts a y co-ordinate to a line index.
525      * @param y the y co-ordinate
526      */
527     public int yToLine(int y) {
528         FontMetrics fm = painter.getFontMetrics();
529         int height = fm.getHeight();
530 
531         return Math.max(0, Math.min(getLineCount() - 1,
532                 y / height + firstLine));
533     }
534 
535     /**
536      * Converts an offset in a line into an x co-ordinate. This is a
537      * slow version that can be used any time.
538      * @param line the line
539      * @param offset the offset, from the start of the line
540      */
541     public final int offsetToX(int line, int offset) {
542         // don't use cached tokens
543         painter.currentLineTokens = null;
544         return _offsetToX(line, offset);
545     }
546 
547     /**
548      * Converts an offset in a line into an x co-ordinate. This is a
549      * fast version that should only be used if no changes were made
550      * to the text since the last repaint.
551      * @param line The line
552      * @param offset The offset, from the start of the line
553      */
554     public int _offsetToX(int line, int offset) {
555         TokenMarker tokenMarker = getTokenMarker();
556 
557         /* Use painter's cached info for speed */
558         FontMetrics fm = painter.getFontMetrics();
559 
560         getLineText(line, lineSegment);
561 
562         int segmentOffset = lineSegment.offset;
563         int x = horizontalOffset;
564 
565         /* If syntax coloring is disabled, do simple translation */
566         if (tokenMarker == null) {
567             lineSegment.count = offset;
568             return x + Utilities.getTabbedTextWidth(lineSegment,
569                     fm, x, painter, 0);
570         } /* If syntax coloring is enabled, we have to do this because
571          * tokens can vary in width */ else {
572             Token tokens;
573 
574             if (painter.currentLineIndex == line
575                     && painter.currentLineTokens != null)
576                 tokens = painter.currentLineTokens;
577             else {
578                 painter.currentLineIndex = line;
579                 tokens = painter.currentLineTokens
580                         = tokenMarker.markTokens(lineSegment, line);
581             }
582 
583             Toolkit toolkit = painter.getToolkit();
584             Font defaultFont = painter.getFont();
585             SyntaxStyle[] styles = painter.getStyles();
586 
587             for (;;) {
588                 byte id = tokens.id;
589 
590                 if (id == Token.END) {
591                     return x;
592                 }
593 
594                 if (id == Token.NULL)
595                     fm = painter.getFontMetrics();
596                 else
597                     fm = styles[id].getFontMetrics(defaultFont);
598 
599                 int length = tokens.length;
600 
601                 if (offset + segmentOffset < lineSegment.offset + length) {
602                     lineSegment.count = offset
603                             - (lineSegment.offset - segmentOffset);
604                     return x + Utilities.getTabbedTextWidth(
605                             lineSegment, fm, x, painter, 0);
606                 } else {
607                     lineSegment.count = length;
608                     x += Utilities.getTabbedTextWidth(
609                             lineSegment, fm, x, painter, 0);
610                     lineSegment.offset += length;
611                 }
612                 tokens = tokens.next;
613             }
614         }
615     }
616 
617     /**
618      * Converts an x co-ordinate to an offset within a line.
619      * @param line The line
620      * @param x The x co-ordinate
621      */
622     public int xToOffset(int line, int x) {
623         TokenMarker tokenMarker = getTokenMarker();
624 
625         /* Use painter's cached info for speed */
626         FontMetrics fm = painter.getFontMetrics();
627 
628         getLineText(line, lineSegment);
629 
630         char[] segmentArray = lineSegment.array;
631         int segmentOffset = lineSegment.offset;
632         int segmentCount = lineSegment.count;
633 
634         int width = horizontalOffset;
635 
636         if (tokenMarker == null) {
637             for (int i = 0; i < segmentCount; i++) {
638                 char c = segmentArray[i + segmentOffset];
639                 int charWidth;
640 
641                 if (c == '\t')
642                     charWidth = (int) painter.nextTabStop(width, i) - width;
643                 else
644                     charWidth = fm.charWidth(c);
645 
646                 if (painter.isBlockCaretEnabled()) {
647                     if (x - charWidth <= width)
648                         return i;
649                 } else {
650                     if (x - charWidth / 2 <= width)
651                         return i;
652                 }
653 
654                 width += charWidth;
655             }
656 
657             return segmentCount;
658         } else {
659             Token tokens;
660 
661             if (painter.currentLineIndex == line
662                     && painter.currentLineTokens != null)
663                 tokens = painter.currentLineTokens;
664             else {
665                 painter.currentLineIndex = line;
666                 tokens = painter.currentLineTokens
667                         = tokenMarker.markTokens(lineSegment, line);
668             }
669 
670             int offset = 0;
671             Toolkit toolkit = painter.getToolkit();
672             Font defaultFont = painter.getFont();
673             SyntaxStyle[] styles = painter.getStyles();
674 
675             for (;;) {
676                 byte id = tokens.id;
677 
678                 if (id == Token.END)
679                     return offset;
680 
681                 if (id == Token.NULL)
682                     fm = painter.getFontMetrics();
683                 else
684                     fm = styles[id].getFontMetrics(defaultFont);
685 
686                 int length = tokens.length;
687 
688                 for (int i = 0; i < length; i++) {
689                     char c = segmentArray[segmentOffset + offset + i];
690                     int charWidth;
691 
692                     if (c == '\t')
693                         charWidth = (int) painter.nextTabStop(width, offset + i)
694                                 - width;
695                     else
696                         charWidth = fm.charWidth(c);
697 
698                     if (painter.isBlockCaretEnabled()) {
699                         if (x - charWidth <= width)
700                             return offset + i;
701                     } else {
702                         if (x - charWidth / 2 <= width)
703                             return offset + i;
704                     }
705 
706                     width += charWidth;
707                 }
708 
709                 offset += length;
710                 tokens = tokens.next;
711             }
712         }
713     }
714 
715     /**
716      * Converts a point to an offset, from the start of the text.
717      * @param x The x co-ordinate of the point
718      * @param y The y co-ordinate of the point
719      */
720     public int xyToOffset(int x, int y) {
721         int line = yToLine(y);
722         int start = getLineStartOffset(line);
723 
724         return start + xToOffset(line, x);
725     }
726 
727     /**
728      * Returns the document this text area is editing.
729      */
730     public SyntaxDocument getDocument() {
731         return document;
732     }
733 
734     /**
735      * Sets the document this text area is editing.
736      * @param document The document
737      */
738     public void setDocument(SyntaxDocument document) {
739         if (this.document == document)
740             return;
741         if (this.document != null)
742             this.document.removeDocumentListener(documentHandler);
743         this.document = document;
744 
745         document.addDocumentListener(documentHandler);
746 
747         select(0, 0);
748         updateScrollBars();
749         painter.repaint();
750     }
751 
752     /**
753      * Sets a new indent length.
754      * @param indent the int value for new indent length
755      */
756     public void setIndent(int indent) {
757         getDocument().setIndent(indent);
758     }
759 
760     /**
761      * Returns the document's token marker. Equivalent to calling
762      * <code>getDocument().getTokenMarker()</code>.
763      */
764     public final TokenMarker getTokenMarker() {
765         return document.getTokenMarker();
766     }
767 
768     /**
769      * Sets the document's token marker. Equivalent to caling
770      * <code>getDocument().setTokenMarker()</code>.
771      * @param tokenMarker The token marker
772      */
773     public final void setTokenMarker(TokenMarker tokenMarker) {
774         document.setTokenMarker(tokenMarker);
775     }
776 
777     /**
778      * Returns the length of the document. Equivalent to calling
779      * <code>getDocument().getLength()</code>.
780      */
781     public int getDocumentLength() {
782         return document.getLength();
783     }
784 
785     /**
786      * Returns the number of lines in the document.
787      */
788     public int getLineCount() {
789         return document.getDefaultRootElement().getElementCount();
790     }
791 
792     /**
793      * Returns the line containing the specified offset.
794      * @param offset The offset
795      */
796     public final int getLineOfOffset(int offset) {
797         return document.getDefaultRootElement().getElementIndex(offset);
798     }
799 
800     /**
801      * Returns the start offset of the specified line.
802      * @param line The line
803      * @return The start offset of the specified line, or -1 if the line is
804      * invalid
805      */
806     public int getLineStartOffset(int line) {
807         Element lineElement = document.getDefaultRootElement().getElement(line);
808 
809         if (lineElement == null)
810             return -1;
811         else
812             return lineElement.getStartOffset();
813     }
814 
815     /**
816      * Returns the end offset of the specified line.
817      * @param line The line
818      * @return The end offset of the specified line, or -1 if the line is
819      * invalid.
820      */
821     public int getLineEndOffset(int line) {
822         Element lineElement = document.getDefaultRootElement().getElement(line);
823 
824         if (lineElement == null)
825             return -1;
826         else
827             return lineElement.getEndOffset();
828     }
829 
830     /**
831      * Returns the length of the specified line.
832      * @param line The line
833      */
834     public int getLineLength(int line) {
835         Element lineElement = document.getDefaultRootElement().getElement(line);
836 
837         if (lineElement == null)
838             return -1;
839         else
840             return lineElement.getEndOffset() - lineElement.getStartOffset() - 1;
841     }
842 
843     /**
844      * Returns the specified substring of the document.
845      * @param start The start offset
846      * @param len The length of the substring
847      * @return The substring, or null if the offsets are invalid
848      */
849     public final String getText(int start, int len) {
850         try {
851             return document.getText(start, len);
852         } catch (BadLocationException bl) {
853 
854             return null;
855         }
856     }
857 
858     /**
859      * Copies the specified substring of the document into a segment.
860      * If the offsets are invalid, the segment will contain a null string.
861      * @param start The start offset
862      * @param len The length of the substring
863      * @param segment The segment
864      */
865     public final void getText(int start, int len, Segment segment) {
866         try {
867             document.getText(start, len, segment);
868         } catch (BadLocationException bl) {
869 
870             segment.offset = segment.count = 0;
871         }
872     }
873 
874     /**
875      * Returns the text on the specified line.
876      * @param lineIndex The line
877      * @return The text, or null if the line is invalid
878      */
879     public String getLineText(int lineIndex) {
880         int start = getLineStartOffset(lineIndex);
881 
882         return getText(start, getLineEndOffset(lineIndex) - start - 1);
883     }
884 
885     /**
886      * Copies the text on the specified line into a segment. If the line
887      * is invalid, the segment will contain a null string.
888      * @param lineIndex The line
889      */
890     public void getLineText(int lineIndex, Segment segment) {
891         int start = getLineStartOffset(lineIndex);
892 
893         getText(start, getLineEndOffset(lineIndex) - start - 1, segment);
894     }
895 
896     /**
897      * Returns the selection start offset.
898      */
899     public final int getSelectionStart() {
900         return selectionStart;
901     }
902 
903     /**
904      * Returns the offset where the selection starts on the specified
905      * line.
906      */
907     public int getSelectionStart(int line) {
908         if (line == selectionStartLine)
909             return selectionStart;
910         else if (rectSelect) {
911             Element map = document.getDefaultRootElement();
912             int start = selectionStart
913                     - map.getElement(selectionStartLine).getStartOffset();
914 
915             Element lineElement = map.getElement(line);
916             int lineStart = lineElement.getStartOffset();
917             int lineEnd = lineElement.getEndOffset() - 1;
918 
919             return Math.min(lineEnd, lineStart + start);
920         } else
921             return getLineStartOffset(line);
922     }
923 
924     /**
925      * Returns the selection start line.
926      */
927     public final int getSelectionStartLine() {
928         return selectionStartLine;
929     }
930 
931     /**
932      * Sets the selection start. The new selection will be the new
933      * selection start and the old selection end.
934      * @param selectionStart The selection start
935      * @see #select(int,int)
936      */
937     public final void setSelectionStart(int selectionStart) {
938         select(selectionStart, selectionEnd);
939     }
940 
941     /**
942      * Returns the selection end offset.
943      */
944     public final int getSelectionEnd() {
945         return selectionEnd;
946     }
947 
948     /**
949      * Returns the offset where the selection ends on the specified
950      * line.
951      */
952     public int getSelectionEnd(int line) {
953         if (line == selectionEndLine)
954             return selectionEnd;
955         else if (rectSelect) {
956             Element map = document.getDefaultRootElement();
957             int end = selectionEnd
958                     - map.getElement(selectionEndLine).getStartOffset();
959 
960             Element lineElement = map.getElement(line);
961             int lineStart = lineElement.getStartOffset();
962             int lineEnd = lineElement.getEndOffset() - 1;
963 
964             return Math.min(lineEnd, lineStart + end);
965         } else
966             return getLineEndOffset(line) - 1;
967     }
968 
969     /**
970      * Returns the selection end line.
971      */
972     public final int getSelectionEndLine() {
973         return selectionEndLine;
974     }
975 
976     /**
977      * Sets the selection end. The new selection will be the old
978      * selection start and the bew selection end.
979      * @param selectionEnd The selection end
980      * @see #select(int,int)
981      */
982     public final void setSelectionEnd(int selectionEnd) {
983         select(selectionStart, selectionEnd);
984     }
985 
986     /**
987      * Returns the caret position. This will either be the selection
988      * start or the selection end, depending on which direction the
989      * selection was made in.
990      */
991     public final int getCaretPosition() {
992         return (biasLeft ? selectionStart : selectionEnd);
993     }
994 
995     /**
996      * Returns the caret line.
997      */
998     public final int getCaretLine() {
999         return (biasLeft ? selectionStartLine : selectionEndLine);
1000    }
1001
1002    /**
1003     * Returns the mark position. This will be the opposite selection
1004     * bound to the caret position.
1005     * @see #getCaretPosition()
1006     */
1007    public final int getMarkPosition() {
1008        return (biasLeft ? selectionEnd : selectionStart);
1009    }
1010
1011    /**
1012     * Returns the mark line.
1013     */
1014    public final int getMarkLine() {
1015        return (biasLeft ? selectionEndLine : selectionStartLine);
1016    }
1017
1018    /**
1019     * Sets the caret position. The new selection will consist of the
1020     * caret position only (hence no text will be selected)
1021     * @param caret The caret position
1022     * @see #select(int,int)
1023     */
1024    public final void setCaretPosition(int caret) {
1025        select(caret, caret);
1026    }
1027
1028    /**
1029     * Selects all text in the document.
1030     */
1031    public final void selectAll() {
1032        select(0, getDocumentLength());
1033    }
1034
1035    /**
1036     * Moves the mark to the caret position.
1037     */
1038    public final void selectNone() {
1039        select(getCaretPosition(), getCaretPosition());
1040    }
1041
1042    /**
1043     * Selects from the start offset to the end offset. This is the
1044     * general selection method used by all other selecting methods.
1045     * The caret position will be start if start &lt; end, and end
1046     * if end &gt; start.
1047     * @param start The start offset
1048     * @param end The end offset
1049     */
1050    public void select(int start, int end) {
1051        int newStart, newEnd;
1052        boolean newBias;
1053
1054        if (start <= end) {
1055            newStart = start;
1056            newEnd = end;
1057            newBias = false;
1058        } else {
1059            newStart = end;
1060            newEnd = start;
1061            newBias = true;
1062        }
1063
1064        if (newStart < 0 || newEnd > getDocumentLength()) {
1065            throw new IllegalArgumentException("Bounds out of range: " + newStart + "," + newEnd);
1066        }
1067
1068        // If the new position is the same as the old, we don't
1069        // do all this crap, however we still do the stuff at
1070        // the end (clearing magic position, scrolling)
1071        if (newStart != selectionStart || newEnd != selectionEnd
1072                || newBias != biasLeft) {
1073            int newStartLine = getLineOfOffset(newStart);
1074            int newEndLine = getLineOfOffset(newEnd);
1075
1076            if (painter.isBracketHighlightEnabled()) {
1077                if (bracketLine != -1)
1078                    painter.invalidateLine(bracketLine);
1079                updateBracketHighlight(end);
1080                if (bracketLine != -1)
1081                    painter.invalidateLine(bracketLine);
1082            }
1083
1084            painter.invalidateLineRange(selectionStartLine, selectionEndLine);
1085            painter.invalidateLineRange(newStartLine, newEndLine);
1086
1087            // repaint the gutter if the current line changes and current
1088            // line highlighting is enabled
1089            if ((newStartLine != selectionStartLine
1090                    || newEndLine != selectionEndLine || newBias != biasLeft)
1091                    && mGutter.isCurrentLineHighlightEnabled()) {
1092                mGutter.invalidateLine(biasLeft ? selectionStartLine : selectionEndLine);
1093                mGutter.invalidateLine(newBias ? newStartLine : newEndLine);
1094            }
1095
1096            document.addUndoableEdit(new CaretUndo(
1097                    selectionStart, selectionEnd));
1098
1099            selectionStart = newStart;
1100            selectionEnd = newEnd;
1101            selectionStartLine = newStartLine;
1102            selectionEndLine = newEndLine;
1103            biasLeft = newBias;
1104
1105            fireCaretEvent();
1106        }
1107
1108        // When the user is typing, etc, we don't want the caret to blink
1109        blink = true;
1110        caretTimer.restart();
1111
1112        // Disable rectangle select if selection start = selection end
1113        if (selectionStart == selectionEnd)
1114            rectSelect = false;
1115
1116        // Clear the `magic' caret position used by up/down
1117        magicCaret = -1;
1118
1119        scrollToCaret();
1120    }
1121
1122    /**
1123     * Returns the selected text, or null if no selection is active.
1124     */
1125    public final String getSelectedText() {
1126        if (selectionStart == selectionEnd)
1127            return null;
1128
1129        if (rectSelect) {
1130            // Return each row of the selection on a new line
1131
1132            Element map = document.getDefaultRootElement();
1133
1134            int start = selectionStart
1135                    - map.getElement(selectionStartLine).getStartOffset();
1136            int end = selectionEnd
1137                    - map.getElement(selectionEndLine).getStartOffset();
1138
1139            // Certain rectangles satisfy this condition...
1140            if (end < start) {
1141                int tmp = end;
1142
1143                end = start;
1144                start = tmp;
1145            }
1146
1147            StringBuffer buf = new StringBuffer();
1148            Segment seg = new Segment();
1149
1150            for (int i = selectionStartLine; i <= selectionEndLine; i++) {
1151                Element lineElement = map.getElement(i);
1152                int lineStart = lineElement.getStartOffset();
1153                int lineEnd = lineElement.getEndOffset() - 1;
1154                int lineLen = lineEnd - lineStart;
1155
1156                lineStart = Math.min(lineStart + start, lineEnd);
1157                lineLen = Math.min(end - start, lineEnd - lineStart);
1158
1159                getText(lineStart, lineLen, seg);
1160                buf.append(seg.array, seg.offset, seg.count);
1161
1162                if (i != selectionEndLine)
1163                    buf.append(lineSep);
1164            }
1165
1166            return buf.toString();
1167        } else {
1168            return getText(selectionStart, selectionEnd - selectionStart);
1169        }
1170    }
1171
1172    /**
1173     * Replaces the selection with the specified text.
1174     * @param selectedText The replacement text for the selection
1175     */
1176    public void setSelectedText(String selectedText) {
1177    if (indent) tab = "    ";
1178        if (!editable) {
1179            throw new InternalError("Text component read only");
1180        }
1181
1182        document.beginCompoundEdit();
1183
1184        try {
1185            if (rectSelect) {
1186                Element map = document.getDefaultRootElement();
1187
1188                int start = selectionStart - map.getElement(selectionStartLine).getStartOffset();
1189                int end = selectionEnd - map.getElement(selectionEndLine).getStartOffset();
1190
1191                // Certain rectangles satisfy this condition...
1192                if (end < start) {
1193                    int tmp = end;
1194
1195                    end = start;
1196                    start = tmp;
1197                }
1198
1199                int lastNewline = 0;
1200                int currNewline = 0;
1201
1202                for (int i = selectionStartLine; i <= selectionEndLine; i++) {
1203                    Element lineElement = map.getElement(i);
1204                    int lineStart = lineElement.getStartOffset();
1205                    int lineEnd = lineElement.getEndOffset() - 1;
1206                    int rectStart = Math.min(lineEnd, lineStart + start);
1207
1208                    document.remove(rectStart, Math.min(lineEnd - rectStart,
1209                            end - start));
1210
1211                    if (selectedText == null)
1212                        continue;
1213
1214                    currNewline = selectedText.indexOf(lineSep, lastNewline);
1215                    if (currNewline == -1)
1216                        currNewline = selectedText.length();
1217
1218                    document.insertString(rectStart, tab + selectedText.substring(lastNewline, currNewline), null);
1219
1220                    lastNewline = Math.min(selectedText.length(),
1221                            currNewline + 1);
1222                }
1223
1224                if (selectedText != null && currNewline != selectedText.length()) {
1225                    int offset = map.getElement(selectionEndLine).getEndOffset()
1226                            - 1;
1227
1228                    document.insertString(offset, lineSep, null);
1229                    document.insertString(offset + 1, selectedText.substring(currNewline + 1), null);
1230                }
1231            } else {
1232                document.remove(selectionStart,
1233                        selectionEnd - selectionStart);
1234                if (selectedText != null) {
1235                    document.insertString(selectionStart,
1236                            selectedText, null);
1237                }
1238            }
1239        } catch (BadLocationException bl) {
1240
1241            throw new InternalError("Cannot replace selection");
1242        }
1243        // No matter what happens, avoid leaving the document in a bad state
1244        finally {
1245            document.endCompoundEdit();
1246        }
1247
1248        setCaretPosition(selectionEnd);
1249    }
1250
1251    /**
1252     * Returns true if this text area is editable, false otherwise.
1253     */
1254    public final boolean isEditable() {
1255        return editable;
1256    }
1257
1258    /**
1259     * Sets if this component is editable.
1260     * @param editable True if this text area should be editable, false otherwise
1261     */
1262    public final void setEditable(boolean editable) {
1263        this.editable = editable;
1264    }
1265
1266    /**
1267     * Returns the right click popup menu.
1268     */
1269    public final JPopupMenu getRightClickPopup() {
1270        return popup;
1271    }
1272
1273    /**
1274     * Sets the right click popup menu.
1275     * @param popup The popup
1276     */
1277    public final void setRightClickPopup(JPopupMenu popup) {
1278        this.popup = popup;
1279    }
1280
1281    /**
1282     * Returns the `magic' caret position. This can be used to preserve
1283     * the column position when moving up and down lines.
1284     */
1285    public final int getMagicCaretPosition() {
1286        return magicCaret;
1287    }
1288
1289    /**
1290     * Sets the `magic' caret position. This can be used to preserve
1291     * the column position when moving up and down lines.
1292     * @param magicCaret The magic caret position
1293     */
1294    public final void setMagicCaretPosition(int magicCaret) {
1295        this.magicCaret = magicCaret;
1296    }
1297
1298    /**
1299     * Similar to <code>setSelectedText()</code>, but overstrikes the
1300     * appropriate number of characters if overwrite mode is enabled.
1301     * @param str The string
1302     * @see #setSelectedText(String)
1303     * @see #isOverwriteEnabled()
1304     */
1305    public void overwriteSetSelectedText(String str) {
1306        // Don't overstrike if there is a selection
1307        if (!overwrite || selectionStart != selectionEnd) {
1308            setSelectedText(str);
1309            return;
1310        }
1311
1312        // Don't overstrike if we are on the end of the line
1313        int caret = getCaretPosition();
1314        int caretLineEnd = getLineEndOffset(getCaretLine());
1315
1316        if (caretLineEnd - caret <= str.length()) {
1317            setSelectedText(str);
1318            return;
1319        }
1320
1321        document.beginCompoundEdit();
1322
1323        try {
1324            document.remove(caret, str.length());
1325            document.insertString(caret, str, null);
1326        } catch (BadLocationException bl) {
1327            bl.printStackTrace();
1328        } finally {
1329            document.endCompoundEdit();
1330        }
1331    }
1332
1333    /**
1334     * Returns true if overwrite mode is enabled, false otherwise.
1335     */
1336    public final boolean isOverwriteEnabled() {
1337        return overwrite;
1338    }
1339
1340    /**
1341     * Sets if overwrite mode should be enabled.
1342     * @param overwrite True if overwrite mode should be enabled, false otherwise.
1343     */
1344    public final void setOverwriteEnabled(boolean overwrite) {
1345        this.overwrite = overwrite;
1346        painter.invalidateSelectedLines();
1347    }
1348
1349    /**
1350     * Returns true if the selection is rectangular, false otherwise.
1351     */
1352    public final boolean isSelectionRectangular() {
1353        return rectSelect;
1354    }
1355
1356    /**
1357     * Sets if the selection should be rectangular.
1358     * @param overwrite True if the selection should be rectangular,
1359     * false otherwise.
1360     */
1361    public final void setSelectionRectangular(boolean rectSelect) {
1362        this.rectSelect = rectSelect;
1363        painter.invalidateSelectedLines();
1364    }
1365
1366    /**
1367     * Returns the position of the highlighted bracket (the bracket
1368     * matching the one before the caret)
1369     */
1370    public final int getBracketPosition() {
1371        return bracketPosition;
1372    }
1373
1374    /**
1375     * Returns the line of the highlighted bracket (the bracket
1376     * matching the one before the caret)
1377     */
1378    public final int getBracketLine() {
1379        return bracketLine;
1380    }
1381
1382    /**
1383     * Adds a caret change listener to this text area.
1384     * @param listener The listener
1385     */
1386    public final void addCaretListener(CaretListener listener) {
1387        listenerList.add(CaretListener.class, listener);
1388    }
1389
1390    /**
1391     * Removes a caret change listener from this text area.
1392     * @param listener The listener
1393     */
1394    public final void removeCaretListener(CaretListener listener) {
1395        listenerList.remove(CaretListener.class, listener);
1396    }
1397
1398    /**
1399     * Deletes the selected text from the text area and places it
1400     * into the clipboard.
1401     */
1402    public void cut() {
1403        if (editable) {
1404            copy();
1405            setSelectedText("");
1406        }
1407    }
1408
1409    /**
1410     * Places the selected text into the clipboard.
1411     */
1412    public void copy() {
1413        if (selectionStart != selectionEnd) {
1414            Clipboard clipboard = getToolkit().getSystemClipboard();
1415
1416            String selection = getSelectedText();
1417
1418            int repeatCount = inputHandler.getRepeatCount();
1419            StringBuffer buf = new StringBuffer();
1420
1421            for (int i = 0; i < repeatCount; i++)
1422                buf.append(selection);
1423
1424            clipboard.setContents(new StringSelection(buf.toString()), null);
1425        }
1426    }
1427
1428    /**
1429     * Inserts the clipboard contents into the text.
1430     */
1431    public void paste() {
1432        if (editable) {
1433            Clipboard clipboard = getToolkit().getSystemClipboard();
1434
1435            try {
1436                // The MacOS MRJ doesn't convert \r to \n, so do it here
1437                String selection = ((String) clipboard.getContents(this).getTransferData(
1438                                DataFlavor.stringFlavor)).replace('\r', '\n');
1439
1440                int repeatCount = inputHandler.getRepeatCount();
1441                StringBuffer buf = new StringBuffer();
1442
1443                for (int i = 0; i < repeatCount; i++)
1444                    buf.append(selection);
1445                selection = buf.toString();
1446                setSelectedText(selection);
1447            } catch (Exception e) {
1448                getToolkit().beep();
1449                GUIUtilities.showExceptionErrorMessage("Clipboard does not contain a string");
1450            }
1451        }
1452    }
1453
1454    /**
1455     * Undoes the most recent edit. Returns true if the undo was successful.
1456     */
1457    public boolean undo() {
1458        if (editable) return getDocument().undo();
1459        else return false;
1460    }
1461
1462    /**
1463     * Redoes the most recently undone edit. Returns true if the redo was successful.
1464     */
1465    public boolean redo() {
1466        if (editable) return getDocument().redo();
1467        else return false;
1468    }
1469
1470    /**
1471     * Indents the selected lines.
1472     */
1473    public void indent() {
1474
1475    if (selectionStart == selectionEnd)
1476      return;
1477
1478    Element map = document.getDefaultRootElement();
1479
1480    int start = selectionStart - map.getElement(selectionStartLine).getStartOffset();
1481    int end = selectionEnd - map.getElement(selectionEndLine).getStartOffset();
1482
1483    // Certain rectangles satisfy this condition...
1484    if (end < start) {
1485      int tmp = end;
1486      end = start;
1487      start = tmp;
1488    }
1489
1490    StringBuffer buf = new StringBuffer();
1491    Segment seg = new Segment();
1492
1493    for (int i = selectionStartLine; i <= selectionEndLine; i++) {
1494      Element lineElement = map.getElement(i);
1495      int lineStart = lineElement.getStartOffset();
1496      int lineEnd = lineElement.getEndOffset() - 1;
1497      int lineLen = lineEnd - lineStart;
1498
1499      lineStart = Math.min(lineStart + start, lineEnd);
1500      lineLen = Math.max(end - start, lineEnd - lineStart);