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