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