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

Quick Search    Search Deep

Source code: com/memoire/jedit/JEditTextArea.java


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