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