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