OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

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