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