Statistics
| Revision:

svn-gvsig-desktop / tags / v1_0_2_Build_910 / libraries / libIverUtiles / src / com / iver / utiles / console / jedit / JEditTextArea.java @ 11275

History | View | Annotate | Download (48.4 KB)

1
package com.iver.utiles.console.jedit;
2
/*
3
 * JEditTextArea.java - jEdit's text component
4
 * Copyright (C) 1999 Slava Pestov
5
 *
6
 * You may use and modify this package for any purpose. Redistribution is
7
 * permitted, in both source and binary form, provided that this notice
8
 * remains intact in all source distributions of this package.
9
 */
10

    
11
import java.awt.AWTEvent;
12
import java.awt.Component;
13
import java.awt.Container;
14
import java.awt.Dimension;
15
import java.awt.Font;
16
import java.awt.FontMetrics;
17
import java.awt.Insets;
18
import java.awt.LayoutManager;
19
import java.awt.Toolkit;
20
import java.awt.datatransfer.Clipboard;
21
import java.awt.datatransfer.DataFlavor;
22
import java.awt.datatransfer.StringSelection;
23
import java.awt.event.ActionEvent;
24
import java.awt.event.ActionListener;
25
import java.awt.event.AdjustmentEvent;
26
import java.awt.event.AdjustmentListener;
27
import java.awt.event.ComponentAdapter;
28
import java.awt.event.ComponentEvent;
29
import java.awt.event.FocusEvent;
30
import java.awt.event.FocusListener;
31
import java.awt.event.InputEvent;
32
import java.awt.event.KeyEvent;
33
import java.awt.event.MouseAdapter;
34
import java.awt.event.MouseEvent;
35
import java.awt.event.MouseMotionListener;
36
import java.util.Enumeration;
37
import java.util.Vector;
38

    
39
import javax.swing.JComponent;
40
import javax.swing.JPopupMenu;
41
import javax.swing.JScrollBar;
42
import javax.swing.SwingUtilities;
43
import javax.swing.Timer;
44
import javax.swing.event.CaretEvent;
45
import javax.swing.event.CaretListener;
46
import javax.swing.event.DocumentEvent;
47
import javax.swing.event.DocumentListener;
48
import javax.swing.event.EventListenerList;
49
import javax.swing.text.BadLocationException;
50
import javax.swing.text.Document;
51
import javax.swing.text.Element;
52
import javax.swing.text.Segment;
53
import javax.swing.text.Utilities;
54
import javax.swing.undo.AbstractUndoableEdit;
55
import javax.swing.undo.CannotRedoException;
56
import javax.swing.undo.CannotUndoException;
57
import javax.swing.undo.UndoableEdit;
58

    
59
/**
60
 * jEdit's text area component. It is more suited for editing program
61
 * source code than JEditorPane, because it drops the unnecessary features
62
 * (images, variable-width lines, and so on) and adds a whole bunch of
63
 * useful goodies such as:
64
 * <ul>
65
 * <li>More flexible key binding scheme
66
 * <li>Supports macro recorders
67
 * <li>Rectangular selection
68
 * <li>Bracket highlighting
69
 * <li>Syntax highlighting
70
 * <li>Command repetition
71
 * <li>Block caret can be enabled
72
 * </ul>
73
 * It is also faster and doesn't have as many problems. It can be used
74
 * in other applications; the only other part of jEdit it depends on is
75
 * the syntax package.<p>
76
 *
77
 * To use it in your app, treat it like any other component, for example:
78
 * <pre>JEditTextArea ta = new JEditTextArea();
79
 * ta.setTokenMarker(new JavaTokenMarker());
80
 * ta.setText("public class Test {\n"
81
 *     + "    public static void main(String[] args) {\n"
82
 *     + "        System.out.println(\"Hello World\");\n"
83
 *     + "    }\n"
84
 *     + "}");</pre>
85
 *
86
 * @author Slava Pestov
87
 * @version $Id$
88
 */
89
public class JEditTextArea extends JComponent
90
{
91
        /**
92
         * Adding components with this name to the text area will place
93
         * them left of the horizontal scroll bar. In jEdit, the status
94
         * bar is added this way.
95
         */
96
        public static String LEFT_OF_SCROLLBAR = "los";
97

    
98
        /**
99
         * Creates a new JEditTextArea with the default settings.
100
         */
101
        public JEditTextArea()
102
        {
103
                this(TextAreaDefaults.getDefaults());
104
        }
105

    
106
        /**
107
         * Creates a new JEditTextArea with the specified settings.
108
         * @param defaults The default settings
109
         */
110
        public JEditTextArea(TextAreaDefaults defaults)
111
        {
112
                // Enable the necessary events
113
                enableEvents(AWTEvent.KEY_EVENT_MASK);
114

    
115
                // Initialize some misc. stuff
116
                painter = new TextAreaPainter(this,defaults);
117
                documentHandler = new DocumentHandler();
118
                listenerList = new EventListenerList();
119
                caretEvent = new MutableCaretEvent();
120
                lineSegment = new Segment();
121
                bracketLine = bracketPosition = -1;
122
                blink = true;
123

    
124
                // Initialize the GUI
125
                setLayout(new ScrollLayout());
126
                add(CENTER,painter);
127
                add(RIGHT,vertical = new JScrollBar(JScrollBar.VERTICAL));
128
                add(BOTTOM,horizontal = new JScrollBar(JScrollBar.HORIZONTAL));
129

    
130
                // Add some event listeners
131
                vertical.addAdjustmentListener(new AdjustHandler());
132
                horizontal.addAdjustmentListener(new AdjustHandler());
133
                painter.addComponentListener(new ComponentHandler());
134
                painter.addMouseListener(new MouseHandler());
135
                painter.addMouseMotionListener(new DragHandler());
136
                addFocusListener(new FocusHandler());
137

    
138
                // Load the defaults
139
                setInputHandler(defaults.inputHandler);
140
                setDocument(defaults.document);
141
                editable = defaults.editable;
142
                caretVisible = defaults.caretVisible;
143
                caretBlinks = defaults.caretBlinks;
144
                electricScroll = defaults.electricScroll;
145

    
146
                popup = defaults.popup;
147

    
148
                // We don't seem to get the initial focus event?
149
                focusedComponent = this;
150
        }
151

    
152
        /**
153
         * Returns if this component can be traversed by pressing
154
         * the Tab key. This returns false.
155
         */
156
        public final boolean isManagingFocus()
157
        {
158
                return true;
159
        }
160

    
161
        /**
162
         * Returns the object responsible for painting this text area.
163
         */
164
        public final TextAreaPainter getPainter()
165
        {
166
                return painter;
167
        }
168

    
169
        /**
170
         * Returns the input handler.
171
         */
172
        public final InputHandler getInputHandler()
173
        {
174
                return inputHandler;
175
        }
176

    
177
        /**
178
         * Sets the input handler.
179
         * @param inputHandler The new input handler
180
         */
181
        public void setInputHandler(InputHandler inputHandler)
182
        {
183
                this.inputHandler = inputHandler;
184
        }
185

    
186
        /**
187
         * Returns true if the caret is blinking, false otherwise.
188
         */
189
        public final boolean isCaretBlinkEnabled()
190
        {
191
                return caretBlinks;
192
        }
193

    
194
        /**
195
         * Toggles caret blinking.
196
         * @param caretBlinks True if the caret should blink, false otherwise
197
         */
198
        public void setCaretBlinkEnabled(boolean caretBlinks)
199
        {
200
                this.caretBlinks = caretBlinks;
201
                if(!caretBlinks)
202
                        blink = false;
203

    
204
                painter.invalidateSelectedLines();
205
        }
206

    
207
        /**
208
         * Returns true if the caret is visible, false otherwise.
209
         */
210
        public final boolean isCaretVisible()
211
        {
212
                return (!caretBlinks || blink) && caretVisible;
213
        }
214

    
215
        /**
216
         * Sets if the caret should be visible.
217
         * @param caretVisible True if the caret should be visible, false
218
         * otherwise
219
         */
220
        public void setCaretVisible(boolean caretVisible)
221
        {
222
                this.caretVisible = caretVisible;
223
                blink = true;
224

    
225
                painter.invalidateSelectedLines();
226
        }
227

    
228
        /**
229
         * Blinks the caret.
230
         */
231
        public final void blinkCaret()
232
        {
233
                if(caretBlinks)
234
                {
235
                        blink = !blink;
236
                        painter.invalidateSelectedLines();
237
                }
238
                else
239
                        blink = true;
240
        }
241

    
242
        /**
243
         * Returns the number of lines from the top and button of the
244
         * text area that are always visible.
245
         */
246
        public final int getElectricScroll()
247
        {
248
                return electricScroll;
249
        }
250

    
251
        /**
252
         * Sets the number of lines from the top and bottom of the text
253
         * area that are always visible
254
         * @param electricScroll The number of lines always visible from
255
         * the top or bottom
256
         */
257
        public final void setElectricScroll(int electricScroll)
258
        {
259
                this.electricScroll = electricScroll;
260
        }
261

    
262
        /**
263
         * Updates the state of the scroll bars. This should be called
264
         * if the number of lines in the document changes, or when the
265
         * size of the text are changes.
266
         */
267
        public void updateScrollBars()
268
        {
269
                if(vertical != null && visibleLines != 0)
270
                {
271
                        vertical.setValues(firstLine,visibleLines,0,getLineCount());
272
                        vertical.setUnitIncrement(2);
273
                        vertical.setBlockIncrement(visibleLines);
274
                }
275

    
276
                int width = painter.getWidth();
277
                if(horizontal != null && width != 0)
278
                {
279
                        horizontal.setValues(-horizontalOffset,width,0,width * 5);
280
                        horizontal.setUnitIncrement(painter.getFontMetrics()
281
                                .charWidth('w'));
282
                        horizontal.setBlockIncrement(width / 2);
283
                }
284
        }
285

    
286
        /**
287
         * Returns the line displayed at the text area's origin.
288
         */
289
        public final int getFirstLine()
290
        {
291
                return firstLine;
292
        }
293

    
294
        /**
295
         * Sets the line displayed at the text area's origin without
296
         * updating the scroll bars.
297
         */
298
        public void setFirstLine(int firstLine)
299
        {
300
                if(firstLine == this.firstLine)
301
                        return;
302
                int oldFirstLine = this.firstLine;
303
                this.firstLine = firstLine;
304
                if(firstLine != vertical.getValue())
305
                        updateScrollBars();
306
                painter.repaint();
307
        }
308

    
309
        /**
310
         * Returns the number of lines visible in this text area.
311
         */
312
        public final int getVisibleLines()
313
        {
314
                return visibleLines;
315
        }
316

    
317
        /**
318
         * Recalculates the number of visible lines. This should not
319
         * be called directly.
320
         */
321
        public final void recalculateVisibleLines()
322
        {
323
                if(painter == null)
324
                        return;
325
                int height = painter.getHeight();
326
                int lineHeight = painter.getFontMetrics().getHeight();
327
                int oldVisibleLines = visibleLines;
328
                visibleLines = height / lineHeight;
329
                updateScrollBars();
330
        }
331

    
332
        /**
333
         * Returns the horizontal offset of drawn lines.
334
         */
335
        public final int getHorizontalOffset()
336
        {
337
                return horizontalOffset;
338
        }
339

    
340
        /**
341
         * Sets the horizontal offset of drawn lines. This can be used to
342
         * implement horizontal scrolling.
343
         * @param horizontalOffset offset The new horizontal offset
344
         */
345
        public void setHorizontalOffset(int horizontalOffset)
346
        {
347
                if(horizontalOffset == this.horizontalOffset)
348
                        return;
349
                this.horizontalOffset = horizontalOffset;
350
                if(horizontalOffset != horizontal.getValue())
351
                        updateScrollBars();
352
                painter.repaint();
353
        }
354

    
355
        /**
356
         * A fast way of changing both the first line and horizontal
357
         * offset.
358
         * @param firstLine The new first line
359
         * @param horizontalOffset The new horizontal offset
360
         * @return True if any of the values were changed, false otherwise
361
         */
362
        public boolean setOrigin(int firstLine, int horizontalOffset)
363
        {
364
                boolean changed = false;
365
                int oldFirstLine = this.firstLine;
366

    
367
                if(horizontalOffset != this.horizontalOffset)
368
                {
369
                        this.horizontalOffset = horizontalOffset;
370
                        changed = true;
371
                }
372

    
373
                if(firstLine != this.firstLine)
374
                {
375
                        this.firstLine = firstLine;
376
                        changed = true;
377
                }
378

    
379
                if(changed)
380
                {
381
                        updateScrollBars();
382
                        painter.repaint();
383
                }
384

    
385
                return changed;
386
        }
387

    
388
        /**
389
         * Ensures that the caret is visible by scrolling the text area if
390
         * necessary.
391
         * @return True if scrolling was actually performed, false if the
392
         * caret was already visible
393
         */
394
        public boolean scrollToCaret()
395
        {
396
                int line = getCaretLine();
397
                int lineStart = getLineStartOffset(line);
398
                int offset = Math.max(0,Math.min(getLineLength(line) - 1,
399
                        getCaretPosition() - lineStart));
400

    
401
                return scrollTo(line,offset);
402
        }
403

    
404
        /**
405
         * Ensures that the specified line and offset is visible by scrolling
406
         * the text area if necessary.
407
         * @param line The line to scroll to
408
         * @param offset The offset in the line to scroll to
409
         * @return True if scrolling was actually performed, false if the
410
         * line and offset was already visible
411
         */
412
        public boolean scrollTo(int line, int offset)
413
        {
414
                // visibleLines == 0 before the component is realized
415
                // we can't do any proper scrolling then, so we have
416
                // this hack...
417
                if(visibleLines == 0)
418
                {
419
                        setFirstLine(Math.max(0,line - electricScroll));
420
                        return true;
421
                }
422

    
423
                int newFirstLine = firstLine;
424
                int newHorizontalOffset = horizontalOffset;
425

    
426
                if(line < firstLine + electricScroll)
427
                {
428
                        newFirstLine = Math.max(0,line - electricScroll);
429
                }
430
                else if(line + electricScroll >= firstLine + visibleLines)
431
                {
432
                        newFirstLine = (line - visibleLines) + electricScroll + 1;
433
                        if(newFirstLine + visibleLines >= getLineCount())
434
                                newFirstLine = getLineCount() - visibleLines;
435
                        if(newFirstLine < 0)
436
                                newFirstLine = 0;
437
                }
438

    
439
                int x = _offsetToX(line,offset);
440
                int width = painter.getFontMetrics().charWidth('w');
441

    
442
                if(x < 0)
443
                {
444
                        newHorizontalOffset = Math.min(0,horizontalOffset
445
                                - x + width + 5);
446
                }
447
                else if(x + width >= painter.getWidth())
448
                {
449
                        newHorizontalOffset = horizontalOffset +
450
                                (painter.getWidth() - x) - width - 5;
451
                }
452

    
453
                return setOrigin(newFirstLine,newHorizontalOffset);
454
        }
455

    
456
        /**
457
         * Converts a line index to a y co-ordinate.
458
         * @param line The line
459
         */
460
        public int lineToY(int line)
461
        {
462
                FontMetrics fm = painter.getFontMetrics();
463
                return (line - firstLine) * fm.getHeight()
464
                        - (fm.getLeading() + fm.getMaxDescent());
465
        }
466

    
467
        /**
468
         * Converts a y co-ordinate to a line index.
469
         * @param y The y co-ordinate
470
         */
471
        public int yToLine(int y)
472
        {
473
                FontMetrics fm = painter.getFontMetrics();
474
                int height = fm.getHeight();
475
                return Math.max(0,Math.min(getLineCount() - 1,
476
                        y / height + firstLine));
477
        }
478

    
479
        /**
480
         * Converts an offset in a line into an x co-ordinate. This is a
481
         * slow version that can be used any time.
482
         * @param line The line
483
         * @param offset The offset, from the start of the line
484
         */
485
        public final int offsetToX(int line, int offset)
486
        {
487
                // don't use cached tokens
488
                painter.currentLineTokens = null;
489
                return _offsetToX(line,offset);
490
        }
491

    
492
        /**
493
         * Converts an offset in a line into an x co-ordinate. This is a
494
         * fast version that should only be used if no changes were made
495
         * to the text since the last repaint.
496
         * @param line The line
497
         * @param offset The offset, from the start of the line
498
         */
499
        public int _offsetToX(int line, int offset)
500
        {
501
                TokenMarker tokenMarker = getTokenMarker();
502

    
503
                /* Use painter's cached info for speed */
504
                FontMetrics fm = painter.getFontMetrics();
505

    
506
                getLineText(line,lineSegment);
507

    
508
                int segmentOffset = lineSegment.offset;
509
                int x = horizontalOffset;
510

    
511
                /* If syntax coloring is disabled, do simple translation */
512
                if(tokenMarker == null)
513
                {
514
                        lineSegment.count = offset;
515
                        return x + Utilities.getTabbedTextWidth(lineSegment,
516
                                fm,x,painter,0);
517
                }
518
                /* If syntax coloring is enabled, we have to do this because
519
                 * tokens can vary in width */
520
                else
521
                {
522
                        Token tokens;
523
                        if(painter.currentLineIndex == line
524
                                && painter.currentLineTokens != null)
525
                                tokens = painter.currentLineTokens;
526
                        else
527
                        {
528
                                painter.currentLineIndex = line;
529
                                tokens = painter.currentLineTokens
530
                                        = tokenMarker.markTokens(lineSegment,line);
531
                        }
532

    
533
                        Toolkit toolkit = painter.getToolkit();
534
                        Font defaultFont = painter.getFont();
535
                        SyntaxStyle[] styles = painter.getStyles();
536

    
537
                        for(;;)
538
                        {
539
                                byte id = tokens.id;
540
                                if(id == Token.END)
541
                                {
542
                                        return x;
543
                                }
544

    
545
                                if(id == Token.NULL)
546
                                        fm = painter.getFontMetrics();
547
                                else
548
                                        fm = styles[id].getFontMetrics(defaultFont);
549

    
550
                                int length = tokens.length;
551

    
552
                                if(offset + segmentOffset < lineSegment.offset + length)
553
                                {
554
                                        lineSegment.count = offset - (lineSegment.offset - segmentOffset);
555
                                        return x + Utilities.getTabbedTextWidth(
556
                                                lineSegment,fm,x,painter,0);
557
                                }
558
                                else
559
                                {
560
                                        lineSegment.count = length;
561
                                        x += Utilities.getTabbedTextWidth(
562
                                                lineSegment,fm,x,painter,0);
563
                                        lineSegment.offset += length;
564
                                }
565
                                tokens = tokens.next;
566
                        }
567
                }
568
        }
569

    
570
        /**
571
         * Converts an x co-ordinate to an offset within a line.
572
         * @param line The line
573
         * @param x The x co-ordinate
574
         */
575
        public int xToOffset(int line, int x)
576
        {
577
                TokenMarker tokenMarker = getTokenMarker();
578

    
579
                /* Use painter's cached info for speed */
580
                FontMetrics fm = painter.getFontMetrics();
581

    
582
                getLineText(line,lineSegment);
583

    
584
                char[] segmentArray = lineSegment.array;
585
                int segmentOffset = lineSegment.offset;
586
                int segmentCount = lineSegment.count;
587

    
588
                int width = horizontalOffset;
589

    
590
                if(tokenMarker == null)
591
                {
592
                        for(int i = 0; i < segmentCount; i++)
593
                        {
594
                                char c = segmentArray[i + segmentOffset];
595
                                int charWidth;
596
                                if(c == '\t')
597
                                        charWidth = (int)painter.nextTabStop(width,i)
598
                                                - width;
599
                                else
600
                                        charWidth = fm.charWidth(c);
601

    
602
                                if(painter.isBlockCaretEnabled())
603
                                {
604
                                        if(x - charWidth <= width)
605
                                                return i;
606
                                }
607
                                else
608
                                {
609
                                        if(x - charWidth / 2 <= width)
610
                                                return i;
611
                                }
612

    
613
                                width += charWidth;
614
                        }
615

    
616
                        return segmentCount;
617
                }
618
                else
619
                {
620
                        Token tokens;
621
                        if(painter.currentLineIndex == line && painter
622
                                .currentLineTokens != null)
623
                                tokens = painter.currentLineTokens;
624
                        else
625
                        {
626
                                painter.currentLineIndex = line;
627
                                tokens = painter.currentLineTokens
628
                                        = tokenMarker.markTokens(lineSegment,line);
629
                        }
630

    
631
                        int offset = 0;
632
                        Toolkit toolkit = painter.getToolkit();
633
                        Font defaultFont = painter.getFont();
634
                        SyntaxStyle[] styles = painter.getStyles();
635

    
636
                        for(;;)
637
                        {
638
                                byte id = tokens.id;
639
                                if(id == Token.END)
640
                                        return offset;
641

    
642
                                if(id == Token.NULL)
643
                                        fm = painter.getFontMetrics();
644
                                else
645
                                        fm = styles[id].getFontMetrics(defaultFont);
646

    
647
                                int length = tokens.length;
648

    
649
                                for(int i = 0; i < length; i++)
650
                                {
651
                                        char c = segmentArray[segmentOffset + offset + i];
652
                                        int charWidth;
653
                                        if(c == '\t')
654
                                                charWidth = (int)painter.nextTabStop(width,offset + i)
655
                                                        - width;
656
                                        else
657
                                                charWidth = fm.charWidth(c);
658

    
659
                                        if(painter.isBlockCaretEnabled())
660
                                        {
661
                                                if(x - charWidth <= width)
662
                                                        return offset + i;
663
                                        }
664
                                        else
665
                                        {
666
                                                if(x - charWidth / 2 <= width)
667
                                                        return offset + i;
668
                                        }
669

    
670
                                        width += charWidth;
671
                                }
672

    
673
                                offset += length;
674
                                tokens = tokens.next;
675
                        }
676
                }
677
        }
678

    
679
        /**
680
         * Converts a point to an offset, from the start of the text.
681
         * @param x The x co-ordinate of the point
682
         * @param y The y co-ordinate of the point
683
         */
684
        public int xyToOffset(int x, int y)
685
        {
686
                int line = yToLine(y);
687
                int start = getLineStartOffset(line);
688
                return start + xToOffset(line,x);
689
        }
690

    
691
        /**
692
         * Returns the document this text area is editing.
693
         */
694
        public final Document getDocument()
695
        {
696
                return document;
697
        }
698

    
699
        /**
700
         * Sets the document this text area is editing.
701
         * @param document The document
702
         */
703
        public void setDocument(SyntaxDocument document)
704
        {
705
                if(this.document == document)
706
                        return;
707
                if(this.document != null)
708
                        this.document.removeDocumentListener(documentHandler);
709
                this.document = document;
710

    
711
                document.addDocumentListener(documentHandler);
712

    
713
                select(0,0);
714
                updateScrollBars();
715
                painter.repaint();
716
        }
717

    
718
        /**
719
         * Returns the document's token marker. Equivalent to calling
720
         * <code>getDocument().getTokenMarker()</code>.
721
         */
722
        public final TokenMarker getTokenMarker()
723
        {
724
                return document.getTokenMarker();
725
        }
726

    
727
        /**
728
         * Sets the document's token marker. Equivalent to caling
729
         * <code>getDocument().setTokenMarker()</code>.
730
         * @param tokenMarker The token marker
731
         */
732
        public final void setTokenMarker(TokenMarker tokenMarker)
733
        {
734
                document.setTokenMarker(tokenMarker);
735
        }
736

    
737
        /**
738
         * Returns the length of the document. Equivalent to calling
739
         * <code>getDocument().getLength()</code>.
740
         */
741
        public final int getDocumentLength()
742
        {
743
                return document.getLength();
744
        }
745

    
746
        /**
747
         * Returns the number of lines in the document.
748
         */
749
        public final int getLineCount()
750
        {
751
                return document.getDefaultRootElement().getElementCount();
752
        }
753

    
754
        /**
755
         * Returns the line containing the specified offset.
756
         * @param offset The offset
757
         */
758
        public final int getLineOfOffset(int offset)
759
        {
760
                return document.getDefaultRootElement().getElementIndex(offset);
761
        }
762

    
763
        /**
764
         * Returns the start offset of the specified line.
765
         * @param line The line
766
         * @return The start offset of the specified line, or -1 if the line is
767
         * invalid
768
         */
769
        public int getLineStartOffset(int line)
770
        {
771
                Element lineElement = document.getDefaultRootElement()
772
                        .getElement(line);
773
                if(lineElement == null)
774
                        return -1;
775
                else
776
                        return lineElement.getStartOffset();
777
        }
778

    
779
        /**
780
         * Returns the end offset of the specified line.
781
         * @param line The line
782
         * @return The end offset of the specified line, or -1 if the line is
783
         * invalid.
784
         */
785
        public int getLineEndOffset(int line)
786
        {
787
                Element lineElement = document.getDefaultRootElement()
788
                        .getElement(line);
789
                if(lineElement == null)
790
                        return -1;
791
                else
792
                        return lineElement.getEndOffset();
793
        }
794

    
795
        /**
796
         * Returns the length of the specified line.
797
         * @param line The line
798
         */
799
        public int getLineLength(int line)
800
        {
801
                Element lineElement = document.getDefaultRootElement()
802
                        .getElement(line);
803
                if(lineElement == null)
804
                        return -1;
805
                else
806
                        return lineElement.getEndOffset()
807
                                - lineElement.getStartOffset() - 1;
808
        }
809

    
810
        /**
811
         * Returns the entire text of this text area.
812
         */
813
        public String getText()
814
        {
815
                try
816
                {
817
                        return document.getText(0,document.getLength());
818
                }
819
                catch(BadLocationException bl)
820
                {
821
                        bl.printStackTrace();
822
                        return null;
823
                }
824
        }
825

    
826
        /**
827
         * Sets the entire text of this text area.
828
         */
829
        public void setText(String text)
830
        {
831
                try
832
                {
833
                        document.beginCompoundEdit();
834
                        document.remove(0,document.getLength());
835
                        document.insertString(0,text,null);
836
                }
837
                catch(BadLocationException bl)
838
                {
839
                        bl.printStackTrace();
840
                }
841
                finally
842
                {
843
                        document.endCompoundEdit();
844
                }
845
        }
846

    
847
        /**
848
         * Returns the specified substring of the document.
849
         * @param start The start offset
850
         * @param len The length of the substring
851
         * @return The substring, or null if the offsets are invalid
852
         */
853
        public final String getText(int start, int len)
854
        {
855
                try
856
                {
857
                        return document.getText(start,len);
858
                }
859
                catch(BadLocationException bl)
860
                {
861
                        bl.printStackTrace();
862
                        return null;
863
                }
864
        }
865

    
866
        /**
867
         * Copies the specified substring of the document into a segment.
868
         * If the offsets are invalid, the segment will contain a null string.
869
         * @param start The start offset
870
         * @param len The length of the substring
871
         * @param segment The segment
872
         */
873
        public final void getText(int start, int len, Segment segment)
874
        {
875
                try
876
                {
877
                        document.getText(start,len,segment);
878
                }
879
                catch(BadLocationException bl)
880
                {
881
                        bl.printStackTrace();
882
                        segment.offset = segment.count = 0;
883
                }
884
        }
885

    
886
        /**
887
         * Returns the text on the specified line.
888
         * @param lineIndex The line
889
         * @return The text, or null if the line is invalid
890
         */
891
        public final String getLineText(int lineIndex)
892
        {
893
                int start = getLineStartOffset(lineIndex);
894
                return getText(start,getLineEndOffset(lineIndex) - start - 1);
895
        }
896

    
897
        /**
898
         * Copies the text on the specified line into a segment. If the line
899
         * is invalid, the segment will contain a null string.
900
         * @param lineIndex The line
901
         */
902
        public final void getLineText(int lineIndex, Segment segment)
903
        {
904
                int start = getLineStartOffset(lineIndex);
905
                getText(start,getLineEndOffset(lineIndex) - start - 1,segment);
906
        }
907

    
908
        /**
909
         * Returns the selection start offset.
910
         */
911
        public final int getSelectionStart()
912
        {
913
                return selectionStart;
914
        }
915

    
916
        /**
917
         * Returns the offset where the selection starts on the specified
918
         * line.
919
         */
920
        public int getSelectionStart(int line)
921
        {
922
                if(line == selectionStartLine)
923
                        return selectionStart;
924
                else if(rectSelect)
925
                {
926
                        Element map = document.getDefaultRootElement();
927
                        int start = selectionStart - map.getElement(selectionStartLine)
928
                                .getStartOffset();
929

    
930
                        Element lineElement = map.getElement(line);
931
                        int lineStart = lineElement.getStartOffset();
932
                        int lineEnd = lineElement.getEndOffset() - 1;
933
                        return Math.min(lineEnd,lineStart + start);
934
                }
935
                else
936
                        return getLineStartOffset(line);
937
        }
938

    
939
        /**
940
         * Returns the selection start line.
941
         */
942
        public final int getSelectionStartLine()
943
        {
944
                return selectionStartLine;
945
        }
946

    
947
        /**
948
         * Sets the selection start. The new selection will be the new
949
         * selection start and the old selection end.
950
         * @param selectionStart The selection start
951
         * @see #select(int,int)
952
         */
953
        public final void setSelectionStart(int selectionStart)
954
        {
955
                select(selectionStart,selectionEnd);
956
        }
957

    
958
        /**
959
         * Returns the selection end offset.
960
         */
961
        public final int getSelectionEnd()
962
        {
963
                return selectionEnd;
964
        }
965

    
966
        /**
967
         * Returns the offset where the selection ends on the specified
968
         * line.
969
         */
970
        public int getSelectionEnd(int line)
971
        {
972
                if(line == selectionEndLine)
973
                        return selectionEnd;
974
                else if(rectSelect)
975
                {
976
                        Element map = document.getDefaultRootElement();
977
                        int end = selectionEnd - map.getElement(selectionEndLine)
978
                                .getStartOffset();
979

    
980
                        Element lineElement = map.getElement(line);
981
                        int lineStart = lineElement.getStartOffset();
982
                        int lineEnd = lineElement.getEndOffset() - 1;
983
                        return Math.min(lineEnd,lineStart + end);
984
                }
985
                else
986
                        return getLineEndOffset(line) - 1;
987
        }
988

    
989
        /**
990
         * Returns the selection end line.
991
         */
992
        public final int getSelectionEndLine()
993
        {
994
                return selectionEndLine;
995
        }
996

    
997
        /**
998
         * Sets the selection end. The new selection will be the old
999
         * selection start and the bew selection end.
1000
         * @param selectionEnd The selection end
1001
         * @see #select(int,int)
1002
         */
1003
        public final void setSelectionEnd(int selectionEnd)
1004
        {
1005
                select(selectionStart,selectionEnd);
1006
        }
1007

    
1008
        /**
1009
         * Returns the caret position. This will either be the selection
1010
         * start or the selection end, depending on which direction the
1011
         * selection was made in.
1012
         */
1013
        public final int getCaretPosition()
1014
        {
1015
                return (biasLeft ? selectionStart : selectionEnd);
1016
        }
1017

    
1018
        /**
1019
         * Returns the caret line.
1020
         */
1021
        public final int getCaretLine()
1022
        {
1023
                return (biasLeft ? selectionStartLine : selectionEndLine);
1024
        }
1025

    
1026
        /**
1027
         * Returns the mark position. This will be the opposite selection
1028
         * bound to the caret position.
1029
         * @see #getCaretPosition()
1030
         */
1031
        public final int getMarkPosition()
1032
        {
1033
                return (biasLeft ? selectionEnd : selectionStart);
1034
        }
1035

    
1036
        /**
1037
         * Returns the mark line.
1038
         */
1039
        public final int getMarkLine()
1040
        {
1041
                return (biasLeft ? selectionEndLine : selectionStartLine);
1042
        }
1043

    
1044
        /**
1045
         * Sets the caret position. The new selection will consist of the
1046
         * caret position only (hence no text will be selected)
1047
         * @param caret The caret position
1048
         * @see #select(int,int)
1049
         */
1050
        public final void setCaretPosition(int caret)
1051
        {
1052
                select(caret,caret);
1053
        }
1054

    
1055
        /**
1056
         * Selects all text in the document.
1057
         */
1058
        public final void selectAll()
1059
        {
1060
                select(0,getDocumentLength());
1061
        }
1062

    
1063
        /**
1064
         * Moves the mark to the caret position.
1065
         */
1066
        public final void selectNone()
1067
        {
1068
                select(getCaretPosition(),getCaretPosition());
1069
        }
1070

    
1071
        /**
1072
         * Selects from the start offset to the end offset. This is the
1073
         * general selection method used by all other selecting methods.
1074
         * The caret position will be start if start &lt; end, and end
1075
         * if end &gt; start.
1076
         * @param start The start offset
1077
         * @param end The end offset
1078
         */
1079
        public void select(int start, int end)
1080
        {
1081
                int newStart, newEnd;
1082
                boolean newBias;
1083
                if(start <= end)
1084
                {
1085
                        newStart = start;
1086
                        newEnd = end;
1087
                        newBias = false;
1088
                }
1089
                else
1090
                {
1091
                        newStart = end;
1092
                        newEnd = start;
1093
                        newBias = true;
1094
                }
1095

    
1096
                if(newStart < 0 || newEnd > getDocumentLength())
1097
                {
1098
                        throw new IllegalArgumentException("Bounds out of"
1099
                                + " range: " + newStart + "," +
1100
                                newEnd);
1101
                }
1102

    
1103
                // If the new position is the same as the old, we don't
1104
                // do all this crap, however we still do the stuff at
1105
                // the end (clearing magic position, scrolling)
1106
                if(newStart != selectionStart || newEnd != selectionEnd
1107
                        || newBias != biasLeft)
1108
                {
1109
                        int newStartLine = getLineOfOffset(newStart);
1110
                        int newEndLine = getLineOfOffset(newEnd);
1111

    
1112
                        if(painter.isBracketHighlightEnabled())
1113
                        {
1114
                                if(bracketLine != -1)
1115
                                        painter.invalidateLine(bracketLine);
1116
                                updateBracketHighlight(end);
1117
                                if(bracketLine != -1)
1118
                                        painter.invalidateLine(bracketLine);
1119
                        }
1120

    
1121
                        painter.invalidateLineRange(selectionStartLine,selectionEndLine);
1122
                        painter.invalidateLineRange(newStartLine,newEndLine);
1123

    
1124
                        document.addUndoableEdit(new CaretUndo(
1125
                                selectionStart,selectionEnd));
1126

    
1127
                        selectionStart = newStart;
1128
                        selectionEnd = newEnd;
1129
                        selectionStartLine = newStartLine;
1130
                        selectionEndLine = newEndLine;
1131
                        biasLeft = newBias;
1132

    
1133
                        fireCaretEvent();
1134
                }
1135

    
1136
                // When the user is typing, etc, we don't want the caret
1137
                // to blink
1138
                blink = true;
1139
                caretTimer.restart();
1140

    
1141
                // Disable rectangle select if selection start = selection end
1142
                if(selectionStart == selectionEnd)
1143
                        rectSelect = false;
1144

    
1145
                // Clear the `magic' caret position used by up/down
1146
                magicCaret = -1;
1147

    
1148
                scrollToCaret();
1149
        }
1150

    
1151
        /**
1152
         * Returns the selected text, or null if no selection is active.
1153
         */
1154
        public final String getSelectedText()
1155
        {
1156
                if(selectionStart == selectionEnd)
1157
                        return null;
1158

    
1159
                if(rectSelect)
1160
                {
1161
                        // Return each row of the selection on a new line
1162

    
1163
                        Element map = document.getDefaultRootElement();
1164

    
1165
                        int start = selectionStart - map.getElement(selectionStartLine)
1166
                                .getStartOffset();
1167
                        int end = selectionEnd - map.getElement(selectionEndLine)
1168
                                .getStartOffset();
1169

    
1170
                        // Certain rectangles satisfy this condition...
1171
                        if(end < start)
1172
                        {
1173
                                int tmp = end;
1174
                                end = start;
1175
                                start = tmp;
1176
                        }
1177

    
1178
                        StringBuffer buf = new StringBuffer();
1179
                        Segment seg = new Segment();
1180

    
1181
                        for(int i = selectionStartLine; i <= selectionEndLine; i++)
1182
                        {
1183
                                Element lineElement = map.getElement(i);
1184
                                int lineStart = lineElement.getStartOffset();
1185
                                int lineEnd = lineElement.getEndOffset() - 1;
1186
                                int lineLen = lineEnd - lineStart;
1187

    
1188
                                lineStart = Math.min(lineStart + start,lineEnd);
1189
                                lineLen = Math.min(end - start,lineEnd - lineStart);
1190

    
1191
                                getText(lineStart,lineLen,seg);
1192
                                buf.append(seg.array,seg.offset,seg.count);
1193

    
1194
                                if(i != selectionEndLine)
1195
                                        buf.append('\n');
1196
                        }
1197

    
1198
                        return buf.toString();
1199
                }
1200
                else
1201
                {
1202
                        return getText(selectionStart,
1203
                                selectionEnd - selectionStart);
1204
                }
1205
        }
1206

    
1207
        /**
1208
         * Replaces the selection with the specified text.
1209
         * @param selectedText The replacement text for the selection
1210
         */
1211
        public void setSelectedText(String selectedText)
1212
        {
1213
                if(!editable)
1214
                {
1215
                        throw new InternalError("Text component"
1216
                                + " read only");
1217
                }
1218

    
1219
                document.beginCompoundEdit();
1220

    
1221
                try
1222
                {
1223
                        if(rectSelect)
1224
                        {
1225
                                Element map = document.getDefaultRootElement();
1226

    
1227
                                int start = selectionStart - map.getElement(selectionStartLine)
1228
                                        .getStartOffset();
1229
                                int end = selectionEnd - map.getElement(selectionEndLine)
1230
                                        .getStartOffset();
1231

    
1232
                                // Certain rectangles satisfy this condition...
1233
                                if(end < start)
1234
                                {
1235
                                        int tmp = end;
1236
                                        end = start;
1237
                                        start = tmp;
1238
                                }
1239

    
1240
                                int lastNewline = 0;
1241
                                int currNewline = 0;
1242

    
1243
                                for(int i = selectionStartLine; i <= selectionEndLine; i++)
1244
                                {
1245
                                        Element lineElement = map.getElement(i);
1246
                                        int lineStart = lineElement.getStartOffset();
1247
                                        int lineEnd = lineElement.getEndOffset() - 1;
1248
                                        int rectStart = Math.min(lineEnd,lineStart + start);
1249

    
1250
                                        document.remove(rectStart,Math.min(lineEnd - rectStart,
1251
                                                end - start));
1252

    
1253
                                        if(selectedText == null)
1254
                                                continue;
1255

    
1256
                                        currNewline = selectedText.indexOf('\n',lastNewline);
1257
                                        if(currNewline == -1)
1258
                                                currNewline = selectedText.length();
1259

    
1260
                                        document.insertString(rectStart,selectedText
1261
                                                .substring(lastNewline,currNewline),null);
1262

    
1263
                                        lastNewline = Math.min(selectedText.length(),
1264
                                                currNewline + 1);
1265
                                }
1266

    
1267
                                if(selectedText != null &&
1268
                                        currNewline != selectedText.length())
1269
                                {
1270
                                        int offset = map.getElement(selectionEndLine)
1271
                                                .getEndOffset() - 1;
1272
                                        document.insertString(offset,"\n",null);
1273
                                        document.insertString(offset + 1,selectedText
1274
                                                .substring(currNewline + 1),null);
1275
                                }
1276
                        }
1277
                        else
1278
                        {
1279
                                document.remove(selectionStart,
1280
                                        selectionEnd - selectionStart);
1281
                                if(selectedText != null)
1282
                                {
1283
                                        document.insertString(selectionStart,
1284
                                                selectedText,null);
1285
                                }
1286
                        }
1287
                }
1288
                catch(BadLocationException bl)
1289
                {
1290
                        bl.printStackTrace();
1291
                        throw new InternalError("Cannot replace"
1292
                                + " selection");
1293
                }
1294
                // No matter what happends... stops us from leaving document
1295
                // in a bad state
1296
                finally
1297
                {
1298
                        document.endCompoundEdit();
1299
                }
1300

    
1301
                setCaretPosition(selectionEnd);
1302
        }
1303

    
1304
        /**
1305
         * Returns true if this text area is editable, false otherwise.
1306
         */
1307
        public final boolean isEditable()
1308
        {
1309
                return editable;
1310
        }
1311

    
1312
        /**
1313
         * Sets if this component is editable.
1314
         * @param editable True if this text area should be editable,
1315
         * false otherwise
1316
         */
1317
        public final void setEditable(boolean editable)
1318
        {
1319
                this.editable = editable;
1320
        }
1321

    
1322
        /**
1323
         * Returns the right click popup menu.
1324
         */
1325
        public final JPopupMenu getRightClickPopup()
1326
        {
1327
                return popup;
1328
        }
1329

    
1330
        /**
1331
         * Sets the right click popup menu.
1332
         * @param popup The popup
1333
         */
1334
        public final void setRightClickPopup(JPopupMenu popup)
1335
        {
1336
                this.popup = popup;
1337
        }
1338

    
1339
        /**
1340
         * Returns the `magic' caret position. This can be used to preserve
1341
         * the column position when moving up and down lines.
1342
         */
1343
        public final int getMagicCaretPosition()
1344
        {
1345
                return magicCaret;
1346
        }
1347

    
1348
        /**
1349
         * Sets the `magic' caret position. This can be used to preserve
1350
         * the column position when moving up and down lines.
1351
         * @param magicCaret The magic caret position
1352
         */
1353
        public final void setMagicCaretPosition(int magicCaret)
1354
        {
1355
                this.magicCaret = magicCaret;
1356
        }
1357

    
1358
        /**
1359
         * Similar to <code>setSelectedText()</code>, but overstrikes the
1360
         * appropriate number of characters if overwrite mode is enabled.
1361
         * @param str The string
1362
         * @see #setSelectedText(String)
1363
         * @see #isOverwriteEnabled()
1364
         */
1365
        public void overwriteSetSelectedText(String str)
1366
        {
1367
                // Don't overstrike if there is a selection
1368
                if(!overwrite || selectionStart != selectionEnd)
1369
                {
1370
                        setSelectedText(str);
1371
                        return;
1372
                }
1373

    
1374
                // Don't overstrike if we're on the end of
1375
                // the line
1376
                int caret = getCaretPosition();
1377
                int caretLineEnd = getLineEndOffset(getCaretLine());
1378
                if(caretLineEnd - caret <= str.length())
1379
                {
1380
                        setSelectedText(str);
1381
                        return;
1382
                }
1383

    
1384
                document.beginCompoundEdit();
1385

    
1386
                try
1387
                {
1388
                        document.remove(caret,str.length());
1389
                        document.insertString(caret,str,null);
1390
                }
1391
                catch(BadLocationException bl)
1392
                {
1393
                        bl.printStackTrace();
1394
                }
1395
                finally
1396
                {
1397
                        document.endCompoundEdit();
1398
                }
1399
        }
1400

    
1401
        /**
1402
         * Returns true if overwrite mode is enabled, false otherwise.
1403
         */
1404
        public final boolean isOverwriteEnabled()
1405
        {
1406
                return overwrite;
1407
        }
1408

    
1409
        /**
1410
         * Sets if overwrite mode should be enabled.
1411
         * @param overwrite True if overwrite mode should be enabled,
1412
         * false otherwise.
1413
         */
1414
        public final void setOverwriteEnabled(boolean overwrite)
1415
        {
1416
                this.overwrite = overwrite;
1417
                painter.invalidateSelectedLines();
1418
        }
1419

    
1420
        /**
1421
         * Returns true if the selection is rectangular, false otherwise.
1422
         */
1423
        public final boolean isSelectionRectangular()
1424
        {
1425
                return rectSelect;
1426
        }
1427

    
1428
        /**
1429
         * Sets if the selection should be rectangular.
1430
         * @param overwrite True if the selection should be rectangular,
1431
         * false otherwise.
1432
         */
1433
        public final void setSelectionRectangular(boolean rectSelect)
1434
        {
1435
                this.rectSelect = rectSelect;
1436
                painter.invalidateSelectedLines();
1437
        }
1438

    
1439
        /**
1440
         * Returns the position of the highlighted bracket (the bracket
1441
         * matching the one before the caret)
1442
         */
1443
        public final int getBracketPosition()
1444
        {
1445
                return bracketPosition;
1446
        }
1447

    
1448
        /**
1449
         * Returns the line of the highlighted bracket (the bracket
1450
         * matching the one before the caret)
1451
         */
1452
        public final int getBracketLine()
1453
        {
1454
                return bracketLine;
1455
        }
1456

    
1457
        /**
1458
         * Adds a caret change listener to this text area.
1459
         * @param listener The listener
1460
         */
1461
        public final void addCaretListener(CaretListener listener)
1462
        {
1463
                listenerList.add(CaretListener.class,listener);
1464
        }
1465

    
1466
        /**
1467
         * Removes a caret change listener from this text area.
1468
         * @param listener The listener
1469
         */
1470
        public final void removeCaretListener(CaretListener listener)
1471
        {
1472
                listenerList.remove(CaretListener.class,listener);
1473
        }
1474

    
1475
        /**
1476
         * Deletes the selected text from the text area and places it
1477
         * into the clipboard.
1478
         */
1479
        public void cut()
1480
        {
1481
                if(editable)
1482
                {
1483
                        copy();
1484
                        setSelectedText("");
1485
                }
1486
        }
1487

    
1488
        /**
1489
         * Places the selected text into the clipboard.
1490
         */
1491
        public void copy()
1492
        {
1493
                if(selectionStart != selectionEnd)
1494
                {
1495
                        Clipboard clipboard = getToolkit().getSystemClipboard();
1496

    
1497
                        String selection = getSelectedText();
1498

    
1499
                        int repeatCount = inputHandler.getRepeatCount();
1500
                        StringBuffer buf = new StringBuffer();
1501
                        for(int i = 0; i < repeatCount; i++)
1502
                                buf.append(selection);
1503

    
1504
                        clipboard.setContents(new StringSelection(buf.toString()),null);
1505
                }
1506
        }
1507

    
1508
        /**
1509
         * Inserts the clipboard contents into the text.
1510
         */
1511
        public void paste()
1512
        {
1513
                if(editable)
1514
                {
1515
                        Clipboard clipboard = getToolkit().getSystemClipboard();
1516
                        try
1517
                        {
1518
                                // The MacOS MRJ doesn't convert \r to \n,
1519
                                // so do it here
1520
                                String selection = ((String)clipboard
1521
                                        .getContents(this).getTransferData(
1522
                                        DataFlavor.stringFlavor))
1523
                                        .replace('\r','\n');
1524

    
1525
                                int repeatCount = inputHandler.getRepeatCount();
1526
                                StringBuffer buf = new StringBuffer();
1527
                                for(int i = 0; i < repeatCount; i++)
1528
                                        buf.append(selection);
1529
                                selection = buf.toString();
1530
                                setSelectedText(selection);
1531
                        }
1532
                        catch(Exception e)
1533
                        {
1534
                                getToolkit().beep();
1535
                                System.err.println("Clipboard does not"
1536
                                        + " contain a string");
1537
                        }
1538
                }
1539
        }
1540

    
1541
        /**
1542
         * Called by the AWT when this component is removed from it's parent.
1543
         * This stops clears the currently focused component.
1544
         */
1545
        public void removeNotify()
1546
        {
1547
                super.removeNotify();
1548
                if(focusedComponent == this)
1549
                        focusedComponent = null;
1550
        }
1551

    
1552
        /**
1553
         * Forwards key events directly to the input handler.
1554
         * This is slightly faster than using a KeyListener
1555
         * because some Swing overhead is avoided.
1556
         */
1557
        public void processKeyEvent(KeyEvent evt)
1558
        {
1559
                if(inputHandler == null)
1560
                        return;
1561
                switch(evt.getID())
1562
                {
1563
                case KeyEvent.KEY_TYPED:
1564
                        inputHandler.keyTyped(evt);
1565
                        break;
1566
                case KeyEvent.KEY_PRESSED:
1567
                        inputHandler.keyPressed(evt);
1568
                        break;
1569
                case KeyEvent.KEY_RELEASED:
1570
                        inputHandler.keyReleased(evt);
1571
                        break;
1572
                }
1573
        }
1574

    
1575
        // protected members
1576
        protected static String CENTER = "center";
1577
        protected static String RIGHT = "right";
1578
        protected static String BOTTOM = "bottom";
1579

    
1580
        protected static JEditTextArea focusedComponent;
1581
        protected static Timer caretTimer;
1582

    
1583
        protected TextAreaPainter painter;
1584

    
1585
        protected JPopupMenu popup;
1586

    
1587
        protected EventListenerList listenerList;
1588
        protected MutableCaretEvent caretEvent;
1589

    
1590
        protected boolean caretBlinks;
1591
        protected boolean caretVisible;
1592
        protected boolean blink;
1593

    
1594
        protected boolean editable;
1595

    
1596
        protected int firstLine;
1597
        protected int visibleLines;
1598
        protected int electricScroll;
1599

    
1600
        protected int horizontalOffset;
1601

    
1602
        protected JScrollBar vertical;
1603
        protected JScrollBar horizontal;
1604
        protected boolean scrollBarsInitialized;
1605

    
1606
        protected InputHandler inputHandler;
1607
        protected SyntaxDocument document;
1608
        protected DocumentHandler documentHandler;
1609

    
1610
        protected Segment lineSegment;
1611

    
1612
        protected int selectionStart;
1613
        protected int selectionStartLine;
1614
        protected int selectionEnd;
1615
        protected int selectionEndLine;
1616
        protected boolean biasLeft;
1617

    
1618
        protected int bracketPosition;
1619
        protected int bracketLine;
1620

    
1621
        protected int magicCaret;
1622
        protected boolean overwrite;
1623
        protected boolean rectSelect;
1624

    
1625
        protected void fireCaretEvent()
1626
        {
1627
                Object[] listeners = listenerList.getListenerList();
1628
                for(int i = listeners.length - 2; i >= 0; i--)
1629
                {
1630
                        if(listeners[i] == CaretListener.class)
1631
                        {
1632
                                ((CaretListener)listeners[i+1]).caretUpdate(caretEvent);
1633
                        }
1634
                }
1635
                super.validate();
1636
        }
1637

    
1638
        protected void updateBracketHighlight(int newCaretPosition)
1639
        {
1640
                if(newCaretPosition == 0)
1641
                {
1642
                        bracketPosition = bracketLine = -1;
1643
                        return;
1644
                }
1645

    
1646
                try
1647
                {
1648
                        int offset = TextUtilities.findMatchingBracket(
1649
                                document,newCaretPosition - 1);
1650
                        if(offset != -1)
1651
                        {
1652
                                bracketLine = getLineOfOffset(offset);
1653
                                bracketPosition = offset - getLineStartOffset(bracketLine);
1654
                                return;
1655
                        }
1656
                }
1657
                catch(BadLocationException bl)
1658
                {
1659
                        bl.printStackTrace();
1660
                }
1661

    
1662
                bracketLine = bracketPosition = -1;
1663
        }
1664

    
1665
        protected void documentChanged(DocumentEvent evt)
1666
        {
1667
                DocumentEvent.ElementChange ch = evt.getChange(
1668
                        document.getDefaultRootElement());
1669

    
1670
                int count;
1671
                if(ch == null)
1672
                        count = 0;
1673
                else
1674
                        count = ch.getChildrenAdded().length -
1675
                                ch.getChildrenRemoved().length;
1676

    
1677
                int line = getLineOfOffset(evt.getOffset());
1678
                if(count == 0)
1679
                {
1680
                        painter.invalidateLine(line);
1681
                }
1682
                // do magic stuff
1683
                else if(line < firstLine)
1684
                {
1685
                        setFirstLine(firstLine + count);
1686
                }
1687
                // end of magic stuff
1688
                else
1689
                {
1690
                        painter.invalidateLineRange(line,firstLine + visibleLines);
1691
                        updateScrollBars();
1692
                }
1693
        }
1694

    
1695
        class ScrollLayout implements LayoutManager
1696
        {
1697
                public void addLayoutComponent(String name, Component comp)
1698
                {
1699
                        if(name.equals(CENTER))
1700
                                center = comp;
1701
                        else if(name.equals(RIGHT))
1702
                                right = comp;
1703
                        else if(name.equals(BOTTOM))
1704
                                bottom = comp;
1705
                        else if(name.equals(LEFT_OF_SCROLLBAR))
1706
                                leftOfScrollBar.addElement(comp);
1707
                }
1708

    
1709
                public void removeLayoutComponent(Component comp)
1710
                {
1711
                        if(center == comp)
1712
                                center = null;
1713
                        if(right == comp)
1714
                                right = null;
1715
                        if(bottom == comp)
1716
                                bottom = null;
1717
                        else
1718
                                leftOfScrollBar.removeElement(comp);
1719
                }
1720

    
1721
                public Dimension preferredLayoutSize(Container parent)
1722
                {
1723
                        Dimension dim = new Dimension();
1724
                        Insets insets = getInsets();
1725
                        dim.width = insets.left + insets.right;
1726
                        dim.height = insets.top + insets.bottom;
1727

    
1728
                        Dimension centerPref = center.getPreferredSize();
1729
                        dim.width += centerPref.width;
1730
                        dim.height += centerPref.height;
1731
                        Dimension rightPref = right.getPreferredSize();
1732
                        dim.width += rightPref.width;
1733
                        Dimension bottomPref = bottom.getPreferredSize();
1734
                        dim.height += bottomPref.height;
1735

    
1736
                        return dim;
1737
                }
1738

    
1739
                public Dimension minimumLayoutSize(Container parent)
1740
                {
1741
                        Dimension dim = new Dimension();
1742
                        Insets insets = getInsets();
1743
                        dim.width = insets.left + insets.right;
1744
                        dim.height = insets.top + insets.bottom;
1745

    
1746
                        Dimension centerPref = center.getMinimumSize();
1747
                        dim.width += centerPref.width;
1748
                        dim.height += centerPref.height;
1749
                        Dimension rightPref = right.getMinimumSize();
1750
                        dim.width += rightPref.width;
1751
                        Dimension bottomPref = bottom.getMinimumSize();
1752
                        dim.height += bottomPref.height;
1753

    
1754
                        return dim;
1755
                }
1756

    
1757
                public void layoutContainer(Container parent)
1758
                {
1759
                        Dimension size = parent.getSize();
1760
                        Insets insets = parent.getInsets();
1761
                        int itop = insets.top;
1762
                        int ileft = insets.left;
1763
                        int ibottom = insets.bottom;
1764
                        int iright = insets.right;
1765

    
1766
                        int rightWidth = right.getPreferredSize().width;
1767
                        int bottomHeight = bottom.getPreferredSize().height;
1768
                        int centerWidth = size.width - rightWidth - ileft - iright;
1769
                        int centerHeight = size.height - bottomHeight - itop - ibottom;
1770

    
1771
                        center.setBounds(
1772
                                ileft,
1773
                                itop,
1774
                                centerWidth,
1775
                                centerHeight);
1776

    
1777
                        right.setBounds(
1778
                                ileft + centerWidth,
1779
                                itop,
1780
                                rightWidth,
1781
                                centerHeight);
1782

    
1783
                        // Lay out all status components, in order
1784
                        Enumeration status = leftOfScrollBar.elements();
1785
                        while(status.hasMoreElements())
1786
                        {
1787
                                Component comp = (Component)status.nextElement();
1788
                                Dimension dim = comp.getPreferredSize();
1789
                                comp.setBounds(ileft,
1790
                                        itop + centerHeight,
1791
                                        dim.width,
1792
                                        bottomHeight);
1793
                                ileft += dim.width;
1794
                        }
1795

    
1796
                        bottom.setBounds(
1797
                                ileft,
1798
                                itop + centerHeight,
1799
                                size.width - rightWidth - ileft - iright,
1800
                                bottomHeight);
1801
                }
1802

    
1803
                // private members
1804
                private Component center;
1805
                private Component right;
1806
                private Component bottom;
1807
                private Vector leftOfScrollBar = new Vector();
1808
        }
1809

    
1810
        static class CaretBlinker implements ActionListener
1811
        {
1812
                public void actionPerformed(ActionEvent evt)
1813
                {
1814
                        if(focusedComponent != null
1815
                                && focusedComponent.hasFocus())
1816
                                focusedComponent.blinkCaret();
1817
                }
1818
        }
1819

    
1820
        class MutableCaretEvent extends CaretEvent
1821
        {
1822
                MutableCaretEvent()
1823
                {
1824
                        super(JEditTextArea.this);
1825
                }
1826

    
1827
                public int getDot()
1828
                {
1829
                        return getCaretPosition();
1830
                }
1831

    
1832
                public int getMark()
1833
                {
1834
                        return getMarkPosition();
1835
                }
1836
        }
1837

    
1838
        class AdjustHandler implements AdjustmentListener
1839
        {
1840
                public void adjustmentValueChanged(final AdjustmentEvent evt)
1841
                {
1842
                        if(!scrollBarsInitialized)
1843
                                return;
1844

    
1845
                        // If this is not done, mousePressed events accumilate
1846
                        // and the result is that scrolling doesn't stop after
1847
                        // the mouse is released
1848
                        SwingUtilities.invokeLater(new Runnable() {
1849
                                public void run()
1850
                                {
1851
                                        if(evt.getAdjustable() == vertical)
1852
                                                setFirstLine(vertical.getValue());
1853
                                        else
1854
                                                setHorizontalOffset(-horizontal.getValue());
1855
                                }
1856
                        });
1857
                }
1858
        }
1859

    
1860
        class ComponentHandler extends ComponentAdapter
1861
        {
1862
                public void componentResized(ComponentEvent evt)
1863
                {
1864
                        recalculateVisibleLines();
1865
                        scrollBarsInitialized = true;
1866
                }
1867
        }
1868

    
1869
        class DocumentHandler implements DocumentListener
1870
        {
1871
                public void insertUpdate(DocumentEvent evt)
1872
                {
1873
                        documentChanged(evt);
1874

    
1875
                        int offset = evt.getOffset();
1876
                        int length = evt.getLength();
1877

    
1878
                        int newStart;
1879
                        int newEnd;
1880

    
1881
                        if(selectionStart > offset || (selectionStart
1882
                                == selectionEnd && selectionStart == offset))
1883
                                newStart = selectionStart + length;
1884
                        else
1885
                                newStart = selectionStart;
1886

    
1887
                        if(selectionEnd >= offset)
1888
                                newEnd = selectionEnd + length;
1889
                        else
1890
                                newEnd = selectionEnd;
1891

    
1892
                        select(newStart,newEnd);
1893
                }
1894

    
1895
                public void removeUpdate(DocumentEvent evt)
1896
                {
1897
                        documentChanged(evt);
1898

    
1899
                        int offset = evt.getOffset();
1900
                        int length = evt.getLength();
1901

    
1902
                        int newStart;
1903
                        int newEnd;
1904

    
1905
                        if(selectionStart > offset)
1906
                        {
1907
                                if(selectionStart > offset + length)
1908
                                        newStart = selectionStart - length;
1909
                                else
1910
                                        newStart = offset;
1911
                        }
1912
                        else
1913
                                newStart = selectionStart;
1914

    
1915
                        if(selectionEnd > offset)
1916
                        {
1917
                                if(selectionEnd > offset + length)
1918
                                        newEnd = selectionEnd - length;
1919
                                else
1920
                                        newEnd = offset;
1921
                        }
1922
                        else
1923
                                newEnd = selectionEnd;
1924

    
1925
                        select(newStart,newEnd);
1926
                }
1927

    
1928
                public void changedUpdate(DocumentEvent evt)
1929
                {
1930
                }
1931
        }
1932

    
1933
        class DragHandler implements MouseMotionListener
1934
        {
1935
                public void mouseDragged(MouseEvent evt)
1936
                {
1937
                        if(popup != null && popup.isVisible())
1938
                                return;
1939

    
1940
                        setSelectionRectangular((evt.getModifiers()
1941
                                & InputEvent.CTRL_MASK) != 0);
1942
                        select(getMarkPosition(),xyToOffset(evt.getX(),evt.getY()));
1943
                }
1944

    
1945
                public void mouseMoved(MouseEvent evt) {}
1946
        }
1947

    
1948
        class FocusHandler implements FocusListener
1949
        {
1950
                public void focusGained(FocusEvent evt)
1951
                {
1952
                        setCaretVisible(true);
1953
                        focusedComponent = JEditTextArea.this;
1954
                }
1955

    
1956
                public void focusLost(FocusEvent evt)
1957
                {
1958
                        setCaretVisible(false);
1959
                        focusedComponent = null;
1960
                }
1961
        }
1962

    
1963
        class MouseHandler extends MouseAdapter
1964
        {
1965
                public void mousePressed(MouseEvent evt)
1966
                {
1967
                        requestFocus();
1968

    
1969
                        // Focus events not fired sometimes?
1970
                        setCaretVisible(true);
1971
                        focusedComponent = JEditTextArea.this;
1972

    
1973
                        if((evt.getModifiers() & InputEvent.BUTTON3_MASK) != 0
1974
                                && popup != null)
1975
                        {
1976
                                popup.show(painter,evt.getX(),evt.getY());
1977
                                return;
1978
                        }
1979

    
1980
                        int line = yToLine(evt.getY());
1981
                        int offset = xToOffset(line,evt.getX());
1982
                        int dot = getLineStartOffset(line) + offset;
1983

    
1984
                        switch(evt.getClickCount())
1985
                        {
1986
                        case 1:
1987
                                doSingleClick(evt,line,offset,dot);
1988
                                break;
1989
                        case 2:
1990
                                // It uses the bracket matching stuff, so
1991
                                // it can throw a BLE
1992
                                try
1993
                                {
1994
                                        doDoubleClick(evt,line,offset,dot);
1995
                                }
1996
                                catch(BadLocationException bl)
1997
                                {
1998
                                        bl.printStackTrace();
1999
                                }
2000
                                break;
2001
                        case 3:
2002
                                doTripleClick(evt,line,offset,dot);
2003
                                break;
2004
                        }
2005
                }
2006

    
2007
                private void doSingleClick(MouseEvent evt, int line,
2008
                        int offset, int dot)
2009
                {
2010
                        if((evt.getModifiers() & InputEvent.SHIFT_MASK) != 0)
2011
                        {
2012
                                rectSelect = (evt.getModifiers() & InputEvent.CTRL_MASK) != 0;
2013
                                select(getMarkPosition(),dot);
2014
                        }
2015
                        else
2016
                                setCaretPosition(dot);
2017
                }
2018

    
2019
                private void doDoubleClick(MouseEvent evt, int line,
2020
                        int offset, int dot) throws BadLocationException
2021
                {
2022
                        // Ignore empty lines
2023
                        if(getLineLength(line) == 0)
2024
                                return;
2025

    
2026
                        try
2027
                        {
2028
                                int bracket = TextUtilities.findMatchingBracket(
2029
                                        document,Math.max(0,dot - 1));
2030
                                if(bracket != -1)
2031
                                {
2032
                                        int mark = getMarkPosition();
2033
                                        // Hack
2034
                                        if(bracket > mark)
2035
                                        {
2036
                                                bracket++;
2037
                                                mark--;
2038
                                        }
2039
                                        select(mark,bracket);
2040
                                        return;
2041
                                }
2042
                        }
2043
                        catch(BadLocationException bl)
2044
                        {
2045
                                bl.printStackTrace();
2046
                        }
2047

    
2048
                        // Ok, it's not a bracket... select the word
2049
                        String lineText = getLineText(line);
2050
                        char ch = lineText.charAt(Math.max(0,offset - 1));
2051

    
2052
                        String noWordSep = (String)document.getProperty("noWordSep");
2053
                        if(noWordSep == null)
2054
                                noWordSep = "";
2055

    
2056
                        // If the user clicked on a non-letter char,
2057
                        // we select the surrounding non-letters
2058
                        boolean selectNoLetter = (!Character
2059
                                .isLetterOrDigit(ch)
2060
                                && noWordSep.indexOf(ch) == -1);
2061

    
2062
                        int wordStart = 0;
2063

    
2064
                        for(int i = offset - 1; i >= 0; i--)
2065
                        {
2066
                                ch = lineText.charAt(i);
2067
                                if(selectNoLetter ^ (!Character
2068
                                        .isLetterOrDigit(ch) &&
2069
                                        noWordSep.indexOf(ch) == -1))
2070
                                {
2071
                                        wordStart = i + 1;
2072
                                        break;
2073
                                }
2074
                        }
2075

    
2076
                        int wordEnd = lineText.length();
2077
                        for(int i = offset; i < lineText.length(); i++)
2078
                        {
2079
                                ch = lineText.charAt(i);
2080
                                if(selectNoLetter ^ (!Character
2081
                                        .isLetterOrDigit(ch) &&
2082
                                        noWordSep.indexOf(ch) == -1))
2083
                                {
2084
                                        wordEnd = i;
2085
                                        break;
2086
                                }
2087
                        }
2088

    
2089
                        int lineStart = getLineStartOffset(line);
2090
                        select(lineStart + wordStart,lineStart + wordEnd);
2091

    
2092
                        /*
2093
                        String lineText = getLineText(line);
2094
                        String noWordSep = (String)document.getProperty("noWordSep");
2095
                        int wordStart = TextUtilities.findWordStart(lineText,offset,noWordSep);
2096
                        int wordEnd = TextUtilities.findWordEnd(lineText,offset,noWordSep);
2097

2098
                        int lineStart = getLineStartOffset(line);
2099
                        select(lineStart + wordStart,lineStart + wordEnd);
2100
                        */
2101
                }
2102

    
2103
                private void doTripleClick(MouseEvent evt, int line,
2104
                        int offset, int dot)
2105
                {
2106
                        select(getLineStartOffset(line),getLineEndOffset(line)-1);
2107
                }
2108
        }
2109

    
2110
        class CaretUndo extends AbstractUndoableEdit
2111
        {
2112
                private int start;
2113
                private int end;
2114

    
2115
                CaretUndo(int start, int end)
2116
                {
2117
                        this.start = start;
2118
                        this.end = end;
2119
                }
2120

    
2121
                public boolean isSignificant()
2122
                {
2123
                        return false;
2124
                }
2125

    
2126
                public String getPresentationName()
2127
                {
2128
                        return "caret move";
2129
                }
2130

    
2131
                public void undo() throws CannotUndoException
2132
                {
2133
                        super.undo();
2134

    
2135
                        select(start,end);
2136
                }
2137

    
2138
                public void redo() throws CannotRedoException
2139
                {
2140
                        super.redo();
2141

    
2142
                        select(start,end);
2143
                }
2144

    
2145
                public boolean addEdit(UndoableEdit edit)
2146
                {
2147
                        if(edit instanceof CaretUndo)
2148
                        {
2149
                                CaretUndo cedit = (CaretUndo)edit;
2150
                                start = cedit.start;
2151
                                end = cedit.end;
2152
                                cedit.die();
2153

    
2154
                                return true;
2155
                        }
2156
                        else
2157
                                return false;
2158
                }
2159
        }
2160

    
2161
        static
2162
        {
2163
                caretTimer = new Timer(500,new CaretBlinker());
2164
                caretTimer.setInitialDelay(500);
2165
                caretTimer.start();
2166
        }
2167
}