OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

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