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 < end, and end
1028 * if end > 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