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