0001: package net.xoetrope.builder.editor.syntaxhighlight;
0002:
0003: /**
0004: * The syntax highlighting subpackage is a copy of the syntax highlighting
0005: * component offered by syntax.jedit.org or via sourceforge under the MIT
0006: * license. The site does not include the licens file but the MIT license is as
0007: * follows:
0008: *
0009: * The MIT License
0010: * Copyright (c) <year> <copyright holders>
0011: *
0012: * Permission is hereby granted, free of charge, to any person obtaining a copy
0013: * of this software and associated documentation files (the "Software"), to deal
0014: * in the Software without restriction, including without limitation the rights
0015: * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
0016: * copies of the Software, and to permit persons to whom the Software is
0017: * furnished to do so, subject to the following conditions:
0018: *
0019: * The above copyright notice and this permission notice shall be included in
0020: * all copies or substantial portions of the Software.
0021: *
0022: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
0023: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
0024: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
0025: * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
0026: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
0027: * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
0028: * SOFTWARE.s
0029: */
0030:
0031: /*
0032: * JEditTextArea.java - jEdit's text component
0033: * Copyright (C) 1999 Slava Pestov
0034: *
0035: * You may use and modify this package for any purpose. Redistribution is
0036: * permitted, in both source and binary form, provided that this notice
0037: * remains intact in all source distributions of this package.
0038: */
0039:
0040: import java.util.Enumeration;
0041: import java.util.Vector;
0042:
0043: import java.awt.AWTEvent;
0044: import java.awt.Component;
0045: import java.awt.Container;
0046: import java.awt.Dimension;
0047: import java.awt.Font;
0048: import java.awt.FontMetrics;
0049: import java.awt.Insets;
0050: import java.awt.LayoutManager;
0051: import java.awt.Toolkit;
0052: import java.awt.datatransfer.Clipboard;
0053: import java.awt.datatransfer.DataFlavor;
0054: import java.awt.datatransfer.StringSelection;
0055: import java.awt.event.ActionEvent;
0056: import java.awt.event.ActionListener;
0057: import java.awt.event.AdjustmentEvent;
0058: import java.awt.event.AdjustmentListener;
0059: import java.awt.event.ComponentAdapter;
0060: import java.awt.event.ComponentEvent;
0061: import java.awt.event.FocusEvent;
0062: import java.awt.event.FocusListener;
0063: import java.awt.event.InputEvent;
0064: import java.awt.event.KeyEvent;
0065: import java.awt.event.MouseAdapter;
0066: import java.awt.event.MouseEvent;
0067: import java.awt.event.MouseMotionListener;
0068: import javax.swing.JComponent;
0069: import javax.swing.JPopupMenu;
0070: import javax.swing.JScrollBar;
0071: import javax.swing.SwingUtilities;
0072: import javax.swing.Timer;
0073: import javax.swing.event.CaretEvent;
0074: import javax.swing.event.CaretListener;
0075: import javax.swing.event.DocumentEvent;
0076: import javax.swing.event.DocumentListener;
0077: import javax.swing.event.EventListenerList;
0078: import javax.swing.text.BadLocationException;
0079: import javax.swing.text.Element;
0080: import javax.swing.text.Segment;
0081: import javax.swing.text.Utilities;
0082: import javax.swing.undo.AbstractUndoableEdit;
0083: import javax.swing.undo.CannotRedoException;
0084: import javax.swing.undo.CannotUndoException;
0085: import javax.swing.undo.UndoableEdit;
0086:
0087: /**
0088: * jEdit's text area component. It is more suited for editing program
0089: * source code than JEditorPane, because it drops the unnecessary features
0090: * (images, variable-width lines, and so on) and adds a whole bunch of
0091: * useful goodies such as:
0092: * <ul>
0093: * <li>More flexible key binding scheme
0094: * <li>Supports macro recorders
0095: * <li>Rectangular selection
0096: * <li>Bracket highlighting
0097: * <li>Syntax highlighting
0098: * <li>Command repetition
0099: * <li>Block caret can be enabled
0100: * </ul>
0101: * It is also faster and doesn't have as many problems. It can be used
0102: * in other applications; the only other part of jEdit it depends on is
0103: * the syntax package.<p>
0104: *
0105: * To use it in your app, treat it like any other component, for example:
0106: * <pre>JEditTextArea ta = new JEditTextArea();
0107: * ta.setTokenMarker(new JavaTokenMarker());
0108: * ta.setText("public class Test {\n"
0109: * + " public static void main(String[] args) {\n"
0110: * + " System.out.println(\"Hello World\");\n"
0111: * + " }\n"
0112: * + "}");</pre>
0113: *
0114: * @author Slava Pestov
0115: * @version $Id: JEditTextArea.java,v 1.22 2005/01/05 17:20:48 luano Exp $
0116: */
0117: public class JEditTextArea extends JComponent {
0118: /**
0119: * Adding components with this name to the text area will place
0120: * them left of the horizontal scroll bar. In jEdit, the status
0121: * bar is added this way.
0122: */
0123: public static String LEFT_OF_SCROLLBAR = "los";
0124:
0125: /**
0126: * Creates a new JEditTextArea with the default settings.
0127: */
0128: public JEditTextArea() {
0129: this (TextAreaDefaults.getDefaults());
0130: }
0131:
0132: /**
0133: * Creates a new JEditTextArea with the specified settings.
0134: * @param defaults The default settings
0135: */
0136: public JEditTextArea(TextAreaDefaults defaults) {
0137: // Enable the necessary events
0138: enableEvents(AWTEvent.KEY_EVENT_MASK);
0139:
0140: // Initialize some misc. stuff
0141: painter = new TextAreaPainter(this , defaults);
0142: gutter = new Gutter(null, this );
0143: gutter.setFont(painter.getFont());
0144: documentHandler = new DocumentHandler();
0145: listenerList = new EventListenerList();
0146: caretEvent = new MutableCaretEvent();
0147: lineSegment = new Segment();
0148: bracketLine = bracketPosition = -1;
0149: blink = true;
0150:
0151: // Initialize the GUI
0152: setLayout(new ScrollLayout());
0153: add(LEFT, gutter);
0154: add(CENTER, painter);
0155: add(RIGHT, vertical = new JScrollBar(JScrollBar.VERTICAL));
0156: add(BOTTOM, horizontal = new JScrollBar(JScrollBar.HORIZONTAL));
0157:
0158: // Add some event listeners
0159: vertical.addAdjustmentListener(new AdjustHandler());
0160: horizontal.addAdjustmentListener(new AdjustHandler());
0161: painter.addComponentListener(new ComponentHandler());
0162: painter.addMouseListener(new MouseHandler());
0163: painter.addMouseMotionListener(new DragHandler());
0164: addFocusListener(new FocusHandler());
0165:
0166: // Load the defaults
0167: setInputHandler(defaults.inputHandler);
0168: setDocument(defaults.document);
0169: editable = defaults.editable;
0170: caretVisible = defaults.caretVisible;
0171: caretBlinks = defaults.caretBlinks;
0172: electricScroll = defaults.electricScroll;
0173:
0174: popup = defaults.popup;
0175:
0176: // We don't seem to get the initial focus event?
0177: focusedComponent = this ;
0178: }
0179:
0180: /**
0181: * Returns if this component can be traversed by pressing
0182: * the Tab key. This returns false.
0183: */
0184: public final boolean isManagingFocus() {
0185: return true;
0186: }
0187:
0188: /**
0189: * Returns the object responsible for painting this text area.
0190: */
0191: public final TextAreaPainter getPainter() {
0192: return painter;
0193: }
0194:
0195: /**
0196: * Returns the gutter to the left of the text area or null if the gutter
0197: * is disabled
0198: */
0199: public final Gutter getGutter() {
0200: return gutter;
0201: }
0202:
0203: /**
0204: * Returns the input handler.
0205: */
0206: public final InputHandler getInputHandler() {
0207: return inputHandler;
0208: }
0209:
0210: /**
0211: * Sets the input handler.
0212: * @param inputHandler The new input handler
0213: */
0214: public void setInputHandler(InputHandler inputHandler) {
0215: this .inputHandler = inputHandler;
0216: }
0217:
0218: /**
0219: * Returns true if the caret is blinking, false otherwise.
0220: */
0221: public final boolean isCaretBlinkEnabled() {
0222: return caretBlinks;
0223: }
0224:
0225: /**
0226: * Toggles caret blinking.
0227: * @param caretBlinks True if the caret should blink, false otherwise
0228: */
0229: public void setCaretBlinkEnabled(boolean caretBlinks) {
0230: this .caretBlinks = caretBlinks;
0231: if (!caretBlinks)
0232: blink = false;
0233:
0234: painter.invalidateSelectedLines();
0235: }
0236:
0237: /**
0238: * Returns true if the caret is visible, false otherwise.
0239: */
0240: public final boolean isCaretVisible() {
0241: return (!caretBlinks || blink) && caretVisible;
0242: }
0243:
0244: /**
0245: * Sets if the caret should be visible.
0246: * @param caretVisible True if the caret should be visible, false
0247: * otherwise
0248: */
0249: public void setCaretVisible(boolean caretVisible) {
0250: this .caretVisible = caretVisible;
0251: blink = true;
0252:
0253: painter.invalidateSelectedLines();
0254: }
0255:
0256: /**
0257: * Blinks the caret.
0258: */
0259: public final void blinkCaret() {
0260: if (caretBlinks) {
0261: blink = !blink;
0262: painter.invalidateSelectedLines();
0263: } else
0264: blink = true;
0265: }
0266:
0267: /**
0268: * Returns the number of lines from the top and button of the
0269: * text area that are always visible.
0270: */
0271: public final int getElectricScroll() {
0272: return electricScroll;
0273: }
0274:
0275: /**
0276: * Sets the number of lines from the top and bottom of the text
0277: * area that are always visible
0278: * @param electricScroll The number of lines always visible from
0279: * the top or bottom
0280: */
0281: public final void setElectricScroll(int electricScroll) {
0282: this .electricScroll = electricScroll;
0283: }
0284:
0285: /**
0286: * Updates the state of the scroll bars. This should be called
0287: * if the number of lines in the document changes, or when the
0288: * size of the text are changes.
0289: */
0290: public void updateScrollBars() {
0291: if (vertical != null && visibleLines != 0) {
0292: vertical.setValues(firstLine, visibleLines, 0,
0293: getLineCount());
0294: vertical.setUnitIncrement(2);
0295: vertical.setBlockIncrement(visibleLines);
0296: }
0297:
0298: int width = painter.getWidth();
0299: if (horizontal != null && width != 0) {
0300: horizontal
0301: .setValues(-horizontalOffset, width, 0, width * 5);
0302: horizontal.setUnitIncrement(painter.getFontMetrics()
0303: .charWidth('w'));
0304: horizontal.setBlockIncrement(width / 2);
0305: }
0306: }
0307:
0308: /**
0309: * Returns the line displayed at the text area's origin.
0310: */
0311: public final int getFirstLine() {
0312: return firstLine;
0313: }
0314:
0315: /**
0316: * Sets the line displayed at the text area's origin without
0317: * updating the scroll bars.
0318: */
0319: public void setFirstLine(int firstLine) {
0320: if (firstLine == this .firstLine)
0321: return;
0322: int oldFirstLine = this .firstLine;
0323: this .firstLine = firstLine;
0324: if (firstLine != vertical.getValue())
0325: updateScrollBars();
0326: painter.repaint();
0327: gutter.repaint();
0328: }
0329:
0330: /**
0331: * Returns the number of lines visible in this text area.
0332: */
0333: public final int getVisibleLines() {
0334: return visibleLines;
0335: }
0336:
0337: /**
0338: * Recalculates the number of visible lines. This should not
0339: * be called directly.
0340: */
0341: public final void recalculateVisibleLines() {
0342: if (painter == null)
0343: return;
0344: int height = painter.getHeight();
0345: int lineHeight = painter.getFontMetrics().getHeight();
0346: int oldVisibleLines = visibleLines;
0347: visibleLines = height / lineHeight;
0348: updateScrollBars();
0349: }
0350:
0351: /**
0352: * Returns the horizontal offset of drawn lines.
0353: */
0354: public final int getHorizontalOffset() {
0355: return horizontalOffset;
0356: }
0357:
0358: /**
0359: * Sets the horizontal offset of drawn lines. This can be used to
0360: * implement horizontal scrolling.
0361: * @param horizontalOffset offset The new horizontal offset
0362: */
0363: public void setHorizontalOffset(int horizontalOffset) {
0364: if (horizontalOffset == this .horizontalOffset)
0365: return;
0366: this .horizontalOffset = horizontalOffset;
0367: if (horizontalOffset != horizontal.getValue())
0368: updateScrollBars();
0369: painter.repaint();
0370: gutter.repaint();
0371: }
0372:
0373: /**
0374: * A fast way of changing both the first line and horizontal
0375: * offset.
0376: * @param firstLine The new first line
0377: * @param horizontalOffset The new horizontal offset
0378: * @return True if any of the values were changed, false otherwise
0379: */
0380: public boolean setOrigin(int firstLine, int horizontalOffset) {
0381: boolean changed = false;
0382: int oldFirstLine = this .firstLine;
0383:
0384: if (horizontalOffset != this .horizontalOffset) {
0385: this .horizontalOffset = horizontalOffset;
0386: changed = true;
0387: }
0388:
0389: if (firstLine != this .firstLine) {
0390: this .firstLine = firstLine;
0391: changed = true;
0392: }
0393:
0394: if (changed) {
0395: updateScrollBars();
0396: painter.repaint();
0397: gutter.repaint();
0398: }
0399:
0400: return changed;
0401: }
0402:
0403: /**
0404: * Ensures that the caret is visible by scrolling the text area if
0405: * necessary.
0406: * @return True if scrolling was actually performed, false if the
0407: * caret was already visible
0408: */
0409: public boolean scrollToCaret() {
0410: int line = getCaretLine();
0411: int lineStart = getLineStartOffset(line);
0412: int offset = Math.max(0, Math.min(getLineLength(line) - 1,
0413: getCaretPosition() - lineStart));
0414:
0415: return scrollTo(line, offset);
0416: }
0417:
0418: /**
0419: * Ensures that the specified line and offset is visible by scrolling
0420: * the text area if necessary.
0421: * @param line The line to scroll to
0422: * @param offset The offset in the line to scroll to
0423: * @return True if scrolling was actually performed, false if the
0424: * line and offset was already visible
0425: */
0426: public boolean scrollTo(int line, int offset) {
0427: // visibleLines == 0 before the component is realized
0428: // we can't do any proper scrolling then, so we have
0429: // this hack...
0430: if (visibleLines == 0) {
0431: setFirstLine(Math.max(0, line - electricScroll));
0432: return true;
0433: }
0434:
0435: int newFirstLine = firstLine;
0436: int newHorizontalOffset = horizontalOffset;
0437:
0438: if (line < firstLine + electricScroll) {
0439: newFirstLine = Math.max(0, line - electricScroll);
0440: } else if (line + electricScroll >= firstLine + visibleLines) {
0441: newFirstLine = (line - visibleLines) + electricScroll + 1;
0442: if (newFirstLine + visibleLines >= getLineCount())
0443: newFirstLine = getLineCount() - visibleLines;
0444: if (newFirstLine < 0)
0445: newFirstLine = 0;
0446: }
0447:
0448: int x = _offsetToX(line, offset);
0449: int width = painter.getFontMetrics().charWidth('w');
0450:
0451: if (x < 0) {
0452: newHorizontalOffset = Math.min(0, horizontalOffset - x
0453: + width + 5);
0454: } else if (x + width >= painter.getWidth()) {
0455: newHorizontalOffset = horizontalOffset
0456: + (painter.getWidth() - x) - width - 5;
0457: }
0458:
0459: return setOrigin(newFirstLine, newHorizontalOffset);
0460: }
0461:
0462: /**
0463: * Converts a line index to a y co-ordinate.
0464: * @param line The line
0465: */
0466: public int lineToY(int line) {
0467: FontMetrics fm = painter.getFontMetrics();
0468: return (line - firstLine) * fm.getHeight()
0469: - (fm.getLeading() + fm.getMaxDescent());
0470: }
0471:
0472: /**
0473: * Converts a y co-ordinate to a line index.
0474: * @param y The y co-ordinate
0475: */
0476: public int yToLine(int y) {
0477: FontMetrics fm = painter.getFontMetrics();
0478: int height = fm.getHeight();
0479: return Math.max(0, Math.min(getLineCount() - 1, y / height
0480: + firstLine));
0481: }
0482:
0483: /**
0484: * Converts an offset in a line into an x co-ordinate. This is a
0485: * slow version that can be used any time.
0486: * @param line The line
0487: * @param offset The offset, from the start of the line
0488: */
0489: public final int offsetToX(int line, int offset) {
0490: // don't use cached tokens
0491: painter.currentLineTokens = null;
0492: return _offsetToX(line, offset);
0493: }
0494:
0495: /**
0496: * Converts an offset in a line into an x co-ordinate. This is a
0497: * fast version that should only be used if no changes were made
0498: * to the text since the last repaint.
0499: * @param line The line
0500: * @param offset The offset, from the start of the line
0501: */
0502: public int _offsetToX(int line, int offset) {
0503: TokenMarker tokenMarker = getTokenMarker();
0504:
0505: /* Use painter's cached info for speed */
0506: FontMetrics fm = painter.getFontMetrics();
0507:
0508: getLineText(line, lineSegment);
0509:
0510: int segmentOffset = lineSegment.offset;
0511: int x = horizontalOffset;
0512:
0513: /* If syntax coloring is disabled, do simple translation */
0514: if (tokenMarker == null) {
0515: lineSegment.count = offset;
0516: return x
0517: + Utilities.getTabbedTextWidth(lineSegment, fm, x,
0518: painter, 0);
0519: }
0520: /* If syntax coloring is enabled, we have to do this because
0521: * tokens can vary in width */
0522: else {
0523: Token tokens;
0524: if (painter.currentLineIndex == line
0525: && painter.currentLineTokens != null)
0526: tokens = painter.currentLineTokens;
0527: else {
0528: painter.currentLineIndex = line;
0529: tokens = painter.currentLineTokens = tokenMarker
0530: .markTokens(lineSegment, line);
0531: }
0532:
0533: Toolkit toolkit = painter.getToolkit();
0534: Font defaultFont = painter.getFont();
0535: SyntaxStyle[] styles = painter.getStyles();
0536:
0537: for (;;) {
0538: byte id = tokens.id;
0539: if (id == Token.END) {
0540: return x;
0541: }
0542:
0543: if (id == Token.NULL)
0544: fm = painter.getFontMetrics();
0545: else
0546: fm = styles[id].getFontMetrics(defaultFont);
0547:
0548: int length = tokens.length;
0549:
0550: if (offset + segmentOffset < lineSegment.offset
0551: + length) {
0552: lineSegment.count = offset
0553: - (lineSegment.offset - segmentOffset);
0554: return x
0555: + Utilities.getTabbedTextWidth(lineSegment,
0556: fm, x, painter, 0);
0557: } else {
0558: lineSegment.count = length;
0559: x += Utilities.getTabbedTextWidth(lineSegment, fm,
0560: x, painter, 0);
0561: lineSegment.offset += length;
0562: }
0563: tokens = tokens.next;
0564: }
0565: }
0566: }
0567:
0568: /**
0569: * Converts an x co-ordinate to an offset within a line.
0570: * @param line The line
0571: * @param x The x co-ordinate
0572: */
0573: public int xToOffset(int line, int x) {
0574: TokenMarker tokenMarker = getTokenMarker();
0575:
0576: /* Use painter's cached info for speed */
0577: FontMetrics fm = painter.getFontMetrics();
0578:
0579: getLineText(line, lineSegment);
0580:
0581: char[] segmentArray = lineSegment.array;
0582: int segmentOffset = lineSegment.offset;
0583: int segmentCount = lineSegment.count;
0584:
0585: int width = horizontalOffset;
0586:
0587: if (tokenMarker == null) {
0588: for (int i = 0; i < segmentCount; i++) {
0589: char c = segmentArray[i + segmentOffset];
0590: int charWidth;
0591: if (c == '\t')
0592: charWidth = (int) painter.nextTabStop(width, i)
0593: - width;
0594: else
0595: charWidth = fm.charWidth(c);
0596:
0597: if (painter.isBlockCaretEnabled()) {
0598: if (x - charWidth <= width)
0599: return i;
0600: } else {
0601: if (x - charWidth / 2 <= width)
0602: return i;
0603: }
0604:
0605: width += charWidth;
0606: }
0607:
0608: return segmentCount;
0609: } else {
0610: Token tokens;
0611: if (painter.currentLineIndex == line
0612: && painter.currentLineTokens != null)
0613: tokens = painter.currentLineTokens;
0614: else {
0615: painter.currentLineIndex = line;
0616: tokens = painter.currentLineTokens = tokenMarker
0617: .markTokens(lineSegment, line);
0618: }
0619:
0620: int offset = 0;
0621: Toolkit toolkit = painter.getToolkit();
0622: Font defaultFont = painter.getFont();
0623: SyntaxStyle[] styles = painter.getStyles();
0624:
0625: for (;;) {
0626: byte id = tokens.id;
0627: if (id == Token.END)
0628: return offset;
0629:
0630: if (id == Token.NULL)
0631: fm = painter.getFontMetrics();
0632: else
0633: fm = styles[id].getFontMetrics(defaultFont);
0634:
0635: int length = tokens.length;
0636:
0637: for (int i = 0; i < length; i++) {
0638: char c = segmentArray[segmentOffset + offset + i];
0639: int charWidth;
0640: if (c == '\t')
0641: charWidth = (int) painter.nextTabStop(width,
0642: offset + i)
0643: - width;
0644: else
0645: charWidth = fm.charWidth(c);
0646:
0647: if (painter.isBlockCaretEnabled()) {
0648: if (x - charWidth <= width)
0649: return offset + i;
0650: } else {
0651: if (x - charWidth / 2 <= width)
0652: return offset + i;
0653: }
0654:
0655: width += charWidth;
0656: }
0657:
0658: offset += length;
0659: tokens = tokens.next;
0660: }
0661: }
0662: }
0663:
0664: /**
0665: * Converts a point to an offset, from the start of the text.
0666: * @param x The x co-ordinate of the point
0667: * @param y The y co-ordinate of the point
0668: */
0669: public int xyToOffset(int x, int y) {
0670: int line = yToLine(y);
0671: int start = getLineStartOffset(line);
0672: return start + xToOffset(line, x);
0673: }
0674:
0675: /**
0676: * Returns the document this text area is editing.
0677: */
0678: public final SyntaxDocument getDocument() {
0679: return document;
0680: }
0681:
0682: /**
0683: * Sets the document this text area is editing.
0684: * @param document The document
0685: */
0686: public void setDocument(SyntaxDocument document) {
0687: if (this .document == document)
0688: return;
0689: if (this .document != null)
0690: this .document.removeDocumentListener(documentHandler);
0691: this .document = document;
0692:
0693: document.addDocumentListener(documentHandler);
0694:
0695: select(0, 0);
0696: updateScrollBars();
0697: painter.repaint();
0698: gutter.repaint();
0699: }
0700:
0701: /**
0702: * Returns the document's token marker. Equivalent to calling
0703: * <code>getDocument().getTokenMarker()</code>.
0704: */
0705: public final TokenMarker getTokenMarker() {
0706: return document.getTokenMarker();
0707: }
0708:
0709: /**
0710: * Sets the document's token marker. Equivalent to caling
0711: * <code>getDocument().setTokenMarker()</code>.
0712: * @param tokenMarker The token marker
0713: */
0714: public final void setTokenMarker(TokenMarker tokenMarker) {
0715: document.setTokenMarker(tokenMarker);
0716: }
0717:
0718: /**
0719: * Returns the length of the document. Equivalent to calling
0720: * <code>getDocument().getLength()</code>.
0721: */
0722: public final int getDocumentLength() {
0723: return document.getLength();
0724: }
0725:
0726: /**
0727: * Returns the number of lines in the document.
0728: */
0729: public final int getLineCount() {
0730: return document.getDefaultRootElement().getElementCount();
0731: }
0732:
0733: /**
0734: * Returns the line containing the specified offset.
0735: * @param offset The offset
0736: */
0737: public final int getLineOfOffset(int offset) {
0738: return document.getDefaultRootElement().getElementIndex(offset);
0739: }
0740:
0741: /**
0742: * Returns the start offset of the specified line.
0743: * @param line The line
0744: * @return The start offset of the specified line, or -1 if the line is
0745: * invalid
0746: */
0747: public int getLineStartOffset(int line) {
0748: Element lineElement = document.getDefaultRootElement()
0749: .getElement(line);
0750: if (lineElement == null)
0751: return -1;
0752: else
0753: return lineElement.getStartOffset();
0754: }
0755:
0756: /**
0757: * Returns the end offset of the specified line.
0758: * @param line The line
0759: * @return The end offset of the specified line, or -1 if the line is
0760: * invalid.
0761: */
0762: public int getLineEndOffset(int line) {
0763: Element lineElement = document.getDefaultRootElement()
0764: .getElement(line);
0765: if (lineElement == null)
0766: return -1;
0767: else
0768: return lineElement.getEndOffset();
0769: }
0770:
0771: /**
0772: * Returns the length of the specified line.
0773: * @param line The line
0774: */
0775: public int getLineLength(int line) {
0776: Element lineElement = document.getDefaultRootElement()
0777: .getElement(line);
0778: if (lineElement == null)
0779: return -1;
0780: else
0781: return lineElement.getEndOffset()
0782: - lineElement.getStartOffset() - 1;
0783: }
0784:
0785: /**
0786: * Returns the entire text of this text area.
0787: */
0788: public String getText() {
0789: try {
0790: return document.getText(0, document.getLength());
0791: } catch (BadLocationException bl) {
0792: bl.printStackTrace();
0793: return null;
0794: }
0795: }
0796:
0797: /**
0798: * Sets the entire text of this text area.
0799: */
0800: public void setText(String text) {
0801: if (document == null)
0802: return;
0803:
0804: try {
0805: document.beginCompoundEdit();
0806: document.remove(0, document.getLength());
0807: document.insertString(0, text, null);
0808: } catch (BadLocationException bl) {
0809: bl.printStackTrace();
0810: } finally {
0811: document.endCompoundEdit();
0812: }
0813: }
0814:
0815: /**
0816: * Returns the specified substring of the document.
0817: * @param start The start offset
0818: * @param len The length of the substring
0819: * @return The substring, or null if the offsets are invalid
0820: */
0821: public final String getText(int start, int len) {
0822: try {
0823: return document.getText(start, len);
0824: } catch (BadLocationException bl) {
0825: bl.printStackTrace();
0826: return null;
0827: }
0828: }
0829:
0830: /**
0831: * Copies the specified substring of the document into a segment.
0832: * If the offsets are invalid, the segment will contain a null string.
0833: * @param start The start offset
0834: * @param len The length of the substring
0835: * @param segment The segment
0836: */
0837: public final void getText(int start, int len, Segment segment) {
0838: try {
0839: document.getText(start, len, segment);
0840: } catch (BadLocationException bl) {
0841: bl.printStackTrace();
0842: segment.offset = segment.count = 0;
0843: }
0844: }
0845:
0846: /**
0847: * Returns the text on the specified line.
0848: * @param lineIndex The line
0849: * @return The text, or null if the line is invalid
0850: */
0851: public final String getLineText(int lineIndex) {
0852: int start = getLineStartOffset(lineIndex);
0853: return getText(start, getLineEndOffset(lineIndex) - start - 1);
0854: }
0855:
0856: /**
0857: * Copies the text on the specified line into a segment. If the line
0858: * is invalid, the segment will contain a null string.
0859: * @param lineIndex The line
0860: */
0861: public final void getLineText(int lineIndex, Segment segment) {
0862: int start = getLineStartOffset(lineIndex);
0863: getText(start, getLineEndOffset(lineIndex) - start - 1, segment);
0864: }
0865:
0866: /**
0867: * Returns the selection start offset.
0868: */
0869: public final int getSelectionStart() {
0870: return selectionStart;
0871: }
0872:
0873: /**
0874: * Returns the offset where the selection starts on the specified
0875: * line.
0876: */
0877: public int getSelectionStart(int line) {
0878: if (line == selectionStartLine)
0879: return selectionStart;
0880: else if (rectSelect) {
0881: Element map = document.getDefaultRootElement();
0882: int start = selectionStart
0883: - map.getElement(selectionStartLine)
0884: .getStartOffset();
0885:
0886: Element lineElement = map.getElement(line);
0887: int lineStart = lineElement.getStartOffset();
0888: int lineEnd = lineElement.getEndOffset() - 1;
0889: return Math.min(lineEnd, lineStart + start);
0890: } else
0891: return getLineStartOffset(line);
0892: }
0893:
0894: /**
0895: * Returns the selection start line.
0896: */
0897: public final int getSelectionStartLine() {
0898: return selectionStartLine;
0899: }
0900:
0901: /**
0902: * Sets the selection start. The new selection will be the new
0903: * selection start and the old selection end.
0904: * @param selectionStart The selection start
0905: * @see #select(int,int)
0906: */
0907: public final void setSelectionStart(int selectionStart) {
0908: select(selectionStart, selectionEnd);
0909: }
0910:
0911: /**
0912: * Returns the selection end offset.
0913: */
0914: public final int getSelectionEnd() {
0915: return selectionEnd;
0916: }
0917:
0918: /**
0919: * Returns the offset where the selection ends on the specified
0920: * line.
0921: */
0922: public int getSelectionEnd(int line) {
0923: if (line == selectionEndLine)
0924: return selectionEnd;
0925: else if (rectSelect) {
0926: Element map = document.getDefaultRootElement();
0927: int end = selectionEnd
0928: - map.getElement(selectionEndLine).getStartOffset();
0929:
0930: Element lineElement = map.getElement(line);
0931: int lineStart = lineElement.getStartOffset();
0932: int lineEnd = lineElement.getEndOffset() - 1;
0933: return Math.min(lineEnd, lineStart + end);
0934: } else
0935: return getLineEndOffset(line) - 1;
0936: }
0937:
0938: /**
0939: * Returns the selection end line.
0940: */
0941: public final int getSelectionEndLine() {
0942: return selectionEndLine;
0943: }
0944:
0945: /**
0946: * Sets the selection end. The new selection will be the old
0947: * selection start and the bew selection end.
0948: * @param selectionEnd The selection end
0949: * @see #select(int,int)
0950: */
0951: public final void setSelectionEnd(int selectionEnd) {
0952: select(selectionStart, selectionEnd);
0953: }
0954:
0955: /**
0956: * Returns the caret position. This will either be the selection
0957: * start or the selection end, depending on which direction the
0958: * selection was made in.
0959: */
0960: public final int getCaretPosition() {
0961: return (biasLeft ? selectionStart : selectionEnd);
0962: }
0963:
0964: /**
0965: * Returns the caret line.
0966: */
0967: public final int getCaretLine() {
0968: return (biasLeft ? selectionStartLine : selectionEndLine);
0969: }
0970:
0971: /**
0972: * Returns the mark position. This will be the opposite selection
0973: * bound to the caret position.
0974: * @see #getCaretPosition()
0975: */
0976: public final int getMarkPosition() {
0977: return (biasLeft ? selectionEnd : selectionStart);
0978: }
0979:
0980: /**
0981: * Returns the mark line.
0982: */
0983: public final int getMarkLine() {
0984: return (biasLeft ? selectionEndLine : selectionStartLine);
0985: }
0986:
0987: /**
0988: * Sets the caret position. The new selection will consist of the
0989: * caret position only (hence no text will be selected)
0990: * @param caret The caret position
0991: * @see #select(int,int)
0992: */
0993: public final void setCaretPosition(int caret) {
0994: select(caret, caret);
0995: }
0996:
0997: /**
0998: * Selects all text in the document.
0999: */
1000: public final void selectAll() {
1001: select(0, getDocumentLength());
1002: }
1003:
1004: /**
1005: * Moves the mark to the caret position.
1006: */
1007: public final void selectNone() {
1008: select(getCaretPosition(), getCaretPosition());
1009: }
1010:
1011: /**
1012: * Selects from the start offset to the end offset. This is the
1013: * general selection method used by all other selecting methods.
1014: * The caret position will be start if start < end, and end
1015: * if end > start.
1016: * @param start The start offset
1017: * @param end The end offset
1018: */
1019: public void select(int start, int end) {
1020: int newStart, newEnd;
1021: boolean newBias;
1022: if (start <= end) {
1023: newStart = start;
1024: newEnd = end;
1025: newBias = false;
1026: } else {
1027: newStart = end;
1028: newEnd = start;
1029: newBias = true;
1030: }
1031:
1032: if (newStart < 0 || newEnd > getDocumentLength()) {
1033: throw new IllegalArgumentException("Bounds out of"
1034: + " range: " + newStart + "," + newEnd);
1035: }
1036:
1037: // If the new position is the same as the old, we don't
1038: // do all this crap, however we still do the stuff at
1039: // the end (clearing magic position, scrolling)
1040: if (newStart != selectionStart || newEnd != selectionEnd
1041: || newBias != biasLeft) {
1042: int newStartLine = getLineOfOffset(newStart);
1043: int newEndLine = getLineOfOffset(newEnd);
1044:
1045: if (painter.isBracketHighlightEnabled()) {
1046: if (bracketLine != -1)
1047: painter.invalidateLine(bracketLine);
1048: updateBracketHighlight(end);
1049: if (bracketLine != -1)
1050: painter.invalidateLine(bracketLine);
1051: }
1052:
1053: painter.invalidateLineRange(selectionStartLine,
1054: selectionEndLine);
1055: painter.invalidateLineRange(newStartLine, newEndLine);
1056:
1057: document.addUndoableEdit(new CaretUndo(selectionStart,
1058: selectionEnd));
1059:
1060: selectionStart = newStart;
1061: selectionEnd = newEnd;
1062: selectionStartLine = newStartLine;
1063: selectionEndLine = newEndLine;
1064: biasLeft = newBias;
1065:
1066: fireCaretEvent();
1067: }
1068:
1069: // When the user is typing, etc, we don't want the caret
1070: // to blink
1071: blink = true;
1072: caretTimer.restart();
1073:
1074: // Disable rectangle select if selection start = selection end
1075: if (selectionStart == selectionEnd)
1076: rectSelect = false;
1077:
1078: // Clear the `magic' caret position used by up/down
1079: magicCaret = -1;
1080:
1081: scrollToCaret();
1082: }
1083:
1084: /**
1085: * Returns the selected text, or null if no selection is active.
1086: */
1087: public final String getSelectedText() {
1088: if (selectionStart == selectionEnd)
1089: return null;
1090:
1091: if (rectSelect) {
1092: // Return each row of the selection on a new line
1093:
1094: Element map = document.getDefaultRootElement();
1095:
1096: int start = selectionStart
1097: - map.getElement(selectionStartLine)
1098: .getStartOffset();
1099: int end = selectionEnd
1100: - map.getElement(selectionEndLine).getStartOffset();
1101:
1102: // Certain rectangles satisfy this condition...
1103: if (end < start) {
1104: int tmp = end;
1105: end = start;
1106: start = tmp;
1107: }
1108:
1109: StringBuffer buf = new StringBuffer();
1110: Segment seg = new Segment();
1111:
1112: for (int i = selectionStartLine; i <= selectionEndLine; i++) {
1113: Element lineElement = map.getElement(i);
1114: int lineStart = lineElement.getStartOffset();
1115: int lineEnd = lineElement.getEndOffset() - 1;
1116: int lineLen = lineEnd - lineStart;
1117:
1118: lineStart = Math.min(lineStart + start, lineEnd);
1119: lineLen = Math.min(end - start, lineEnd - lineStart);
1120:
1121: getText(lineStart, lineLen, seg);
1122: buf.append(seg.array, seg.offset, seg.count);
1123:
1124: if (i != selectionEndLine)
1125: buf.append('\n');
1126: }
1127:
1128: return buf.toString();
1129: } else {
1130: return getText(selectionStart, selectionEnd
1131: - selectionStart);
1132: }
1133: }
1134:
1135: /**
1136: * Replaces the selection with the specified text.
1137: * @param selectedText The replacement text for the selection
1138: */
1139: public void setSelectedText(String selectedText) {
1140: if (!editable) {
1141: throw new InternalError("Text component" + " read only");
1142: }
1143:
1144: document.beginCompoundEdit();
1145:
1146: try {
1147: if (rectSelect) {
1148: Element map = document.getDefaultRootElement();
1149:
1150: int start = selectionStart
1151: - map.getElement(selectionStartLine)
1152: .getStartOffset();
1153: int end = selectionEnd
1154: - map.getElement(selectionEndLine)
1155: .getStartOffset();
1156:
1157: // Certain rectangles satisfy this condition...
1158: if (end < start) {
1159: int tmp = end;
1160: end = start;
1161: start = tmp;
1162: }
1163:
1164: int lastNewline = 0;
1165: int currNewline = 0;
1166:
1167: for (int i = selectionStartLine; i <= selectionEndLine; i++) {
1168: Element lineElement = map.getElement(i);
1169: int lineStart = lineElement.getStartOffset();
1170: int lineEnd = lineElement.getEndOffset() - 1;
1171: int rectStart = Math
1172: .min(lineEnd, lineStart + start);
1173:
1174: document.remove(rectStart, Math.min(lineEnd
1175: - rectStart, end - start));
1176:
1177: if (selectedText == null)
1178: continue;
1179:
1180: currNewline = selectedText.indexOf('\n',
1181: lastNewline);
1182: if (currNewline == -1)
1183: currNewline = selectedText.length();
1184:
1185: document.insertString(rectStart, selectedText
1186: .substring(lastNewline, currNewline), null);
1187:
1188: lastNewline = Math.min(selectedText.length(),
1189: currNewline + 1);
1190: }
1191:
1192: if (selectedText != null
1193: && currNewline != selectedText.length()) {
1194: int offset = map.getElement(selectionEndLine)
1195: .getEndOffset() - 1;
1196: document.insertString(offset, "\n", null);
1197: document.insertString(offset + 1, selectedText
1198: .substring(currNewline + 1), null);
1199: }
1200: } else {
1201: document.remove(selectionStart, selectionEnd
1202: - selectionStart);
1203: if (selectedText != null) {
1204: document.insertString(selectionStart, selectedText,
1205: null);
1206: }
1207: }
1208: } catch (BadLocationException bl) {
1209: bl.printStackTrace();
1210: throw new InternalError("Cannot replace" + " selection");
1211: }
1212: // No matter what happends... stops us from leaving document
1213: // in a bad state
1214: finally {
1215: document.endCompoundEdit();
1216: }
1217:
1218: setCaretPosition(selectionEnd);
1219: }
1220:
1221: /**
1222: * Returns true if this text area is editable, false otherwise.
1223: */
1224: public final boolean isEditable() {
1225: return editable;
1226: }
1227:
1228: /**
1229: * Sets if this component is editable.
1230: * @param editable True if this text area should be editable,
1231: * false otherwise
1232: */
1233: public final void setEditable(boolean editable) {
1234: this .editable = editable;
1235: }
1236:
1237: /**
1238: * Returns the right click popup menu.
1239: */
1240: public final JPopupMenu getRightClickPopup() {
1241: return popup;
1242: }
1243:
1244: /**
1245: * Sets the right click popup menu.
1246: * @param popup The popup
1247: */
1248: public final void setRightClickPopup(JPopupMenu popup) {
1249: this .popup = popup;
1250: }
1251:
1252: /**
1253: * Returns the `magic' caret position. This can be used to preserve
1254: * the column position when moving up and down lines.
1255: */
1256: public final int getMagicCaretPosition() {
1257: return magicCaret;
1258: }
1259:
1260: /**
1261: * Sets the `magic' caret position. This can be used to preserve
1262: * the column position when moving up and down lines.
1263: * @param magicCaret The magic caret position
1264: */
1265: public final void setMagicCaretPosition(int magicCaret) {
1266: this .magicCaret = magicCaret;
1267: }
1268:
1269: /**
1270: * Similar to <code>setSelectedText()</code>, but overstrikes the
1271: * appropriate number of characters if overwrite mode is enabled.
1272: * @param str The string
1273: * @see #setSelectedText(String)
1274: * @see #isOverwriteEnabled()
1275: */
1276: public void overwriteSetSelectedText(String str) {
1277: // Don't overstrike if there is a selection
1278: if (!overwrite || selectionStart != selectionEnd) {
1279: setSelectedText(str);
1280: return;
1281: }
1282:
1283: // Don't overstrike if we're on the end of
1284: // the line
1285: int caret = getCaretPosition();
1286: int caretLineEnd = getLineEndOffset(getCaretLine());
1287: if (caretLineEnd - caret <= str.length()) {
1288: setSelectedText(str);
1289: return;
1290: }
1291:
1292: document.beginCompoundEdit();
1293:
1294: try {
1295: document.remove(caret, str.length());
1296: document.insertString(caret, str, null);
1297: } catch (BadLocationException bl) {
1298: bl.printStackTrace();
1299: } finally {
1300: document.endCompoundEdit();
1301: }
1302: }
1303:
1304: /**
1305: * Returns true if overwrite mode is enabled, false otherwise.
1306: */
1307: public final boolean isOverwriteEnabled() {
1308: return overwrite;
1309: }
1310:
1311: /**
1312: * Sets if overwrite mode should be enabled.
1313: * @param overwrite True if overwrite mode should be enabled,
1314: * false otherwise.
1315: */
1316: public final void setOverwriteEnabled(boolean overwrite) {
1317: this .overwrite = overwrite;
1318: painter.invalidateSelectedLines();
1319: }
1320:
1321: /**
1322: * Returns true if the selection is rectangular, false otherwise.
1323: */
1324: public final boolean isSelectionRectangular() {
1325: return rectSelect;
1326: }
1327:
1328: /**
1329: * Sets if the selection should be rectangular.
1330: * @param overwrite True if the selection should be rectangular,
1331: * false otherwise.
1332: */
1333: public final void setSelectionRectangular(boolean rectSelect) {
1334: this .rectSelect = rectSelect;
1335: painter.invalidateSelectedLines();
1336: }
1337:
1338: /**
1339: * Returns the position of the highlighted bracket (the bracket
1340: * matching the one before the caret)
1341: */
1342: public final int getBracketPosition() {
1343: return bracketPosition;
1344: }
1345:
1346: /**
1347: * Returns the line of the highlighted bracket (the bracket
1348: * matching the one before the caret)
1349: */
1350: public final int getBracketLine() {
1351: return bracketLine;
1352: }
1353:
1354: /**
1355: * Adds a caret change listener to this text area.
1356: * @param listener The listener
1357: */
1358: public final void addCaretListener(CaretListener listener) {
1359: listenerList.add(CaretListener.class, listener);
1360: }
1361:
1362: /**
1363: * Removes a caret change listener from this text area.
1364: * @param listener The listener
1365: */
1366: public final void removeCaretListener(CaretListener listener) {
1367: listenerList.remove(CaretListener.class, listener);
1368: }
1369:
1370: /**
1371: * Deletes the selected text from the text area and places it
1372: * into the clipboard.
1373: */
1374: public void cut() {
1375: if (editable) {
1376: copy();
1377: setSelectedText("");
1378: }
1379: }
1380:
1381: /**
1382: * Places the selected text into the clipboard.
1383: */
1384: public void copy() {
1385: if (selectionStart != selectionEnd) {
1386: Clipboard clipboard = getToolkit().getSystemClipboard();
1387:
1388: String selection = getSelectedText();
1389:
1390: int repeatCount = inputHandler.getRepeatCount();
1391: StringBuffer buf = new StringBuffer();
1392: for (int i = 0; i < repeatCount; i++)
1393: buf.append(selection);
1394:
1395: clipboard.setContents(new StringSelection(buf.toString()),
1396: null);
1397: }
1398: }
1399:
1400: /**
1401: * Inserts the clipboard contents into the text.
1402: */
1403: public void paste() {
1404: if (editable) {
1405: Clipboard clipboard = getToolkit().getSystemClipboard();
1406: try {
1407: // The MacOS MRJ doesn't convert \r to \n,
1408: // so do it here
1409: String selection = ((String) clipboard
1410: .getContents(this ).getTransferData(
1411: DataFlavor.stringFlavor)).replace('\r',
1412: '\n');
1413:
1414: int repeatCount = inputHandler.getRepeatCount();
1415: StringBuffer buf = new StringBuffer();
1416: for (int i = 0; i < repeatCount; i++)
1417: buf.append(selection);
1418: selection = buf.toString();
1419: setSelectedText(selection);
1420: } catch (Exception e) {
1421: getToolkit().beep();
1422: System.err.println("Clipboard does not"
1423: + " contain a string");
1424: }
1425: }
1426: }
1427:
1428: /**
1429: * Called by the AWT when this component is removed from it's parent.
1430: * This stops clears the currently focused component.
1431: */
1432: public void removeNotify() {
1433: super .removeNotify();
1434: if (focusedComponent == this )
1435: focusedComponent = null;
1436: }
1437:
1438: /**
1439: * Forwards key events directly to the input handler.
1440: * This is slightly faster than using a KeyListener
1441: * because some Swing overhead is avoided.
1442: */
1443: public void processKeyEvent(KeyEvent evt) {
1444: if (inputHandler == null)
1445: return;
1446: switch (evt.getKeyCode()) {
1447: case KeyEvent.VK_F1:
1448: case KeyEvent.VK_F2:
1449: case KeyEvent.VK_F3:
1450: case KeyEvent.VK_F4:
1451: case KeyEvent.VK_F5:
1452: case KeyEvent.VK_F6:
1453: case KeyEvent.VK_F7:
1454: case KeyEvent.VK_F8:
1455: case KeyEvent.VK_F9:
1456: case KeyEvent.VK_F10:
1457: case KeyEvent.VK_F11:
1458: case KeyEvent.VK_F12:
1459: super .processKeyEvent(evt);
1460: return;
1461: }
1462: switch (evt.getID()) {
1463: case KeyEvent.KEY_TYPED:
1464: inputHandler.keyTyped(evt);
1465: break;
1466: case KeyEvent.KEY_PRESSED:
1467: inputHandler.keyPressed(evt);
1468: processMetaKey(evt);
1469: break;
1470: case KeyEvent.KEY_RELEASED:
1471: inputHandler.keyReleased(evt);
1472: break;
1473: }
1474: }
1475:
1476: public void processMetaKey(KeyEvent evt) {
1477: super .processKeyEvent(evt);
1478: }
1479:
1480: /**
1481: * Returns true if the bracket highlight is visible, false otherwise.
1482: */
1483: final boolean isBracketHighlightVisible() {
1484: return ((bracketLine != -1) && hasFocus());
1485: }
1486:
1487: // protected members
1488: protected static String CENTER = "center";
1489: protected static String LEFT = "left";
1490: protected static String RIGHT = "right";
1491: protected static String BOTTOM = "bottom";
1492:
1493: protected static JEditTextArea focusedComponent;
1494: protected static Timer caretTimer;
1495:
1496: protected TextAreaPainter painter;
1497: private Gutter gutter;
1498:
1499: protected JPopupMenu popup;
1500:
1501: protected EventListenerList listenerList;
1502: protected MutableCaretEvent caretEvent;
1503:
1504: protected boolean caretBlinks;
1505: protected boolean caretVisible;
1506: protected boolean blink;
1507:
1508: protected boolean editable;
1509:
1510: protected int firstLine;
1511: protected int visibleLines;
1512: protected int electricScroll;
1513:
1514: protected int horizontalOffset;
1515:
1516: protected JScrollBar vertical;
1517: protected JScrollBar horizontal;
1518: protected boolean scrollBarsInitialized;
1519:
1520: protected InputHandler inputHandler;
1521: protected SyntaxDocument document;
1522: protected DocumentHandler documentHandler;
1523:
1524: protected Segment lineSegment;
1525:
1526: protected int selectionStart;
1527: protected int selectionStartLine;
1528: protected int selectionEnd;
1529: protected int selectionEndLine;
1530: protected boolean biasLeft;
1531:
1532: protected int bracketPosition;
1533: protected int bracketLine;
1534:
1535: protected int magicCaret;
1536: protected boolean overwrite;
1537: protected boolean rectSelect;
1538:
1539: protected void fireCaretEvent() {
1540: Object[] listeners = listenerList.getListenerList();
1541: for (int i = listeners.length - 2; i >= 0; i--) {
1542: if (listeners[i] == CaretListener.class) {
1543: ((CaretListener) listeners[i + 1])
1544: .caretUpdate(caretEvent);
1545: }
1546: }
1547: }
1548:
1549: protected void updateBracketHighlight(int newCaretPosition) {
1550: if (newCaretPosition == 0) {
1551: bracketPosition = bracketLine = -1;
1552: return;
1553: }
1554:
1555: try {
1556: int offset = TextUtilities.findMatchingBracket(document,
1557: newCaretPosition - 1);
1558: if (offset != -1) {
1559: bracketLine = getLineOfOffset(offset);
1560: bracketPosition = offset
1561: - getLineStartOffset(bracketLine);
1562: return;
1563: }
1564: } catch (BadLocationException bl) {
1565: bl.printStackTrace();
1566: }
1567:
1568: bracketLine = bracketPosition = -1;
1569: }
1570:
1571: protected void documentChanged(DocumentEvent evt) {
1572: DocumentEvent.ElementChange ch = evt.getChange(document
1573: .getDefaultRootElement());
1574:
1575: int count;
1576: if (ch == null)
1577: count = 0;
1578: else
1579: count = ch.getChildrenAdded().length
1580: - ch.getChildrenRemoved().length;
1581:
1582: int line = getLineOfOffset(evt.getOffset());
1583: if (count == 0) {
1584: painter.invalidateLine(line);
1585: }
1586: // do magic stuff
1587: else if (line < firstLine) {
1588: setFirstLine(firstLine + count);
1589: }
1590: // end of magic stuff
1591: else {
1592: painter.invalidateLineRange(line, firstLine + visibleLines);
1593: updateScrollBars();
1594: }
1595: }
1596:
1597: class ScrollLayout implements LayoutManager {
1598: public void addLayoutComponent(String name, Component comp) {
1599: if (name.equals(CENTER))
1600: center = comp;
1601: else if (name.equals(LEFT))
1602: left = comp;
1603: else if (name.equals(RIGHT))
1604: right = comp;
1605: else if (name.equals(BOTTOM))
1606: bottom = comp;
1607: else if (name.equals(LEFT_OF_SCROLLBAR))
1608: leftOfScrollBar.addElement(comp);
1609: }
1610:
1611: public void removeLayoutComponent(Component comp) {
1612: if (center == comp)
1613: center = null;
1614: if (left == comp)
1615: left = null;
1616: if (right == comp)
1617: right = null;
1618: if (bottom == comp)
1619: bottom = null;
1620: else
1621: leftOfScrollBar.removeElement(comp);
1622: }
1623:
1624: public Dimension preferredLayoutSize(Container parent) {
1625: Dimension dim = new Dimension();
1626: Insets insets = getInsets();
1627: dim.width = insets.left + insets.right;
1628: dim.height = insets.top + insets.bottom;
1629:
1630: Dimension centerPref = center.getPreferredSize();
1631: dim.width += centerPref.width;
1632: dim.height += centerPref.height;
1633:
1634: Dimension leftPref = left.getPreferredSize();
1635: dim.width += leftPref.width;
1636:
1637: Dimension rightPref = right.getPreferredSize();
1638: dim.width += rightPref.width;
1639:
1640: Dimension bottomPref = bottom.getPreferredSize();
1641: dim.height += bottomPref.height;
1642:
1643: return dim;
1644: }
1645:
1646: public Dimension minimumLayoutSize(Container parent) {
1647: Dimension dim = new Dimension();
1648: Insets insets = getInsets();
1649: dim.width = insets.left + insets.right;
1650: dim.height = insets.top + insets.bottom;
1651:
1652: Dimension centerPref = center.getMinimumSize();
1653: dim.width += centerPref.width;
1654: dim.height += centerPref.height;
1655:
1656: Dimension leftPref = left.getMinimumSize();
1657: dim.width += leftPref.width;
1658:
1659: Dimension rightPref = right.getMinimumSize();
1660: dim.width += rightPref.width;
1661:
1662: Dimension bottomPref = bottom.getMinimumSize();
1663: dim.height += bottomPref.height;
1664:
1665: return dim;
1666: }
1667:
1668: public void layoutContainer(Container parent) {
1669: Dimension size = parent.getSize();
1670: Insets insets = parent.getInsets();
1671: int itop = insets.top;
1672: int ileft = insets.left;
1673: int ibottom = insets.bottom;
1674: int iright = insets.right;
1675:
1676: int leftWidth = left.getPreferredSize().width;
1677: int rightWidth = right.getPreferredSize().width;
1678: int bottomHeight = bottom.getPreferredSize().height;
1679: int centerWidth = size.width - leftWidth - rightWidth
1680: - ileft - iright;
1681: int centerHeight = size.height - bottomHeight - itop
1682: - ibottom;
1683:
1684: left.setBounds(ileft, itop, leftWidth, centerHeight);
1685:
1686: center.setBounds(leftWidth + ileft, itop, centerWidth,
1687: centerHeight);
1688:
1689: right.setBounds(ileft + leftWidth + centerWidth, itop,
1690: rightWidth, centerHeight);
1691:
1692: // Lay out all status components, in order
1693: Enumeration status = leftOfScrollBar.elements();
1694: while (status.hasMoreElements()) {
1695: Component comp = (Component) status.nextElement();
1696: Dimension dim = comp.getPreferredSize();
1697: comp.setBounds(ileft, itop + centerHeight, dim.width,
1698: bottomHeight);
1699: ileft += dim.width;
1700: }
1701:
1702: bottom.setBounds(ileft, itop + centerHeight, size.width
1703: - rightWidth - ileft - iright, bottomHeight);
1704: }
1705:
1706: // private members
1707: private Component center;
1708: private Component left;
1709: private Component right;
1710: private Component bottom;
1711: private Vector leftOfScrollBar = new Vector();
1712: }
1713:
1714: static class CaretBlinker implements ActionListener {
1715: public void actionPerformed(ActionEvent evt) {
1716: if (focusedComponent != null && focusedComponent.hasFocus())
1717: focusedComponent.blinkCaret();
1718: }
1719: }
1720:
1721: class MutableCaretEvent extends CaretEvent {
1722: MutableCaretEvent() {
1723: super (JEditTextArea.this );
1724: }
1725:
1726: public int getDot() {
1727: return getCaretPosition();
1728: }
1729:
1730: public int getMark() {
1731: return getMarkPosition();
1732: }
1733: }
1734:
1735: class AdjustHandler implements AdjustmentListener {
1736: public void adjustmentValueChanged(final AdjustmentEvent evt) {
1737: if (!scrollBarsInitialized)
1738: return;
1739:
1740: // If this is not done, mousePressed events accumilate
1741: // and the result is that scrolling doesn't stop after
1742: // the mouse is released
1743: SwingUtilities.invokeLater(new Runnable() {
1744: public void run() {
1745: if (evt.getAdjustable() == vertical)
1746: setFirstLine(vertical.getValue());
1747: else
1748: setHorizontalOffset(-horizontal.getValue());
1749: }
1750: });
1751: }
1752: }
1753:
1754: class ComponentHandler extends ComponentAdapter {
1755: public void componentResized(ComponentEvent evt) {
1756: recalculateVisibleLines();
1757: scrollBarsInitialized = true;
1758: }
1759: }
1760:
1761: class DocumentHandler implements DocumentListener {
1762: public void insertUpdate(DocumentEvent evt) {
1763: documentChanged(evt);
1764:
1765: int offset = evt.getOffset();
1766: int length = evt.getLength();
1767:
1768: int newStart;
1769: int newEnd;
1770:
1771: if (selectionStart > offset
1772: || (selectionStart == selectionEnd && selectionStart == offset))
1773: newStart = selectionStart + length;
1774: else
1775: newStart = selectionStart;
1776:
1777: if (selectionEnd >= offset)
1778: newEnd = selectionEnd + length;
1779: else
1780: newEnd = selectionEnd;
1781:
1782: select(newStart, newEnd);
1783: }
1784:
1785: public void removeUpdate(DocumentEvent evt) {
1786: documentChanged(evt);
1787:
1788: int offset = evt.getOffset();
1789: int length = evt.getLength();
1790:
1791: int newStart;
1792: int newEnd;
1793:
1794: if (selectionStart > offset) {
1795: if (selectionStart > offset + length)
1796: newStart = selectionStart - length;
1797: else
1798: newStart = offset;
1799: } else
1800: newStart = selectionStart;
1801:
1802: if (selectionEnd > offset) {
1803: if (selectionEnd > offset + length)
1804: newEnd = selectionEnd - length;
1805: else
1806: newEnd = offset;
1807: } else
1808: newEnd = selectionEnd;
1809:
1810: select(newStart, newEnd);
1811: }
1812:
1813: public void changedUpdate(DocumentEvent evt) {
1814: }
1815: }
1816:
1817: class DragHandler implements MouseMotionListener {
1818: public void mouseDragged(MouseEvent evt) {
1819: if (popup != null && popup.isVisible())
1820: return;
1821:
1822: setSelectionRectangular((evt.getModifiers() & InputEvent.CTRL_MASK) != 0);
1823: select(getMarkPosition(),
1824: xyToOffset(evt.getX(), evt.getY()));
1825: }
1826:
1827: public void mouseMoved(MouseEvent evt) {
1828: }
1829: }
1830:
1831: class FocusHandler implements FocusListener {
1832: public void focusGained(FocusEvent evt) {
1833: setCaretVisible(true);
1834: focusedComponent = JEditTextArea.this ;
1835: }
1836:
1837: public void focusLost(FocusEvent evt) {
1838: setCaretVisible(false);
1839: focusedComponent = null;
1840: }
1841: }
1842:
1843: class MouseHandler extends MouseAdapter {
1844: public void mousePressed(MouseEvent evt) {
1845: requestFocus();
1846:
1847: // Focus events not fired sometimes?
1848: setCaretVisible(true);
1849: focusedComponent = JEditTextArea.this ;
1850:
1851: int line = yToLine(evt.getY());
1852: int offset = xToOffset(line, evt.getX());
1853: int dot = getLineStartOffset(line) + offset;
1854:
1855: if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) != 0
1856: && popup != null) {
1857: doSingleClick(evt, line, offset, dot);
1858: popup.show(painter, evt.getX(), evt.getY());
1859: return;
1860: }
1861:
1862: switch (evt.getClickCount()) {
1863: case 1:
1864: doSingleClick(evt, line, offset, dot);
1865: break;
1866: case 2:
1867:
1868: // It uses the bracket matching stuff, so
1869: // it can throw a BLE
1870: try {
1871: doDoubleClick(evt, line, offset, dot);
1872: } catch (BadLocationException bl) {
1873: bl.printStackTrace();
1874: }
1875: break;
1876: case 3:
1877: doTripleClick(evt, line, offset, dot);
1878: break;
1879: }
1880: }
1881:
1882: private void doSingleClick(MouseEvent evt, int line,
1883: int offset, int dot) {
1884: if ((evt.getModifiers() & InputEvent.SHIFT_MASK) != 0) {
1885: rectSelect = (evt.getModifiers() & InputEvent.CTRL_MASK) != 0;
1886: select(getMarkPosition(), dot);
1887: } else
1888: setCaretPosition(dot);
1889: }
1890:
1891: private void doDoubleClick(MouseEvent evt, int line,
1892: int offset, int dot) throws BadLocationException {
1893: // Ignore empty lines
1894: if (getLineLength(line) == 0)
1895: return;
1896:
1897: try {
1898: int bracket = TextUtilities.findMatchingBracket(
1899: document, Math.max(0, dot - 1));
1900: if (bracket != -1) {
1901: int mark = getMarkPosition();
1902: // Hack
1903: if (bracket > mark) {
1904: bracket++;
1905: mark--;
1906: }
1907: select(mark, bracket);
1908: return;
1909: }
1910: } catch (BadLocationException bl) {
1911: bl.printStackTrace();
1912: }
1913:
1914: // Ok, it's not a bracket... select the word
1915: String lineText = getLineText(line);
1916: char ch = lineText.charAt(Math.max(0, offset - 1));
1917:
1918: String noWordSep = (String) document
1919: .getProperty("noWordSep");
1920: if (noWordSep == null)
1921: noWordSep = "";
1922:
1923: // If the user clicked on a non-letter char,
1924: // we select the surrounding non-letters
1925: boolean selectNoLetter = (!Character.isLetterOrDigit(ch) && noWordSep
1926: .indexOf(ch) == -1);
1927:
1928: int wordStart = 0;
1929:
1930: for (int i = offset - 1; i >= 0; i--) {
1931: ch = lineText.charAt(i);
1932: if (selectNoLetter
1933: ^ (!Character.isLetterOrDigit(ch) && noWordSep
1934: .indexOf(ch) == -1)) {
1935: wordStart = i + 1;
1936: break;
1937: }
1938: }
1939:
1940: int wordEnd = lineText.length();
1941: for (int i = offset; i < lineText.length(); i++) {
1942: ch = lineText.charAt(i);
1943: if (selectNoLetter
1944: ^ (!Character.isLetterOrDigit(ch) && noWordSep
1945: .indexOf(ch) == -1)) {
1946: wordEnd = i;
1947: break;
1948: }
1949: }
1950:
1951: int lineStart = getLineStartOffset(line);
1952: select(lineStart + wordStart, lineStart + wordEnd);
1953:
1954: /*
1955: String lineText = getLineText(line);
1956: String noWordSep = (String)document.getProperty("noWordSep");
1957: int wordStart = TextUtilities.findWordStart(lineText,offset,noWordSep);
1958: int wordEnd = TextUtilities.findWordEnd(lineText,offset,noWordSep);
1959: int lineStart = getLineStartOffset(line);
1960: select(lineStart + wordStart,lineStart + wordEnd);
1961: */
1962: }
1963:
1964: private void doTripleClick(MouseEvent evt, int line,
1965: int offset, int dot) {
1966: select(getLineStartOffset(line), getLineEndOffset(line) - 1);
1967: }
1968: }
1969:
1970: class CaretUndo extends AbstractUndoableEdit {
1971: private int start;
1972: private int end;
1973:
1974: CaretUndo(int start, int end) {
1975: this .start = start;
1976: this .end = end;
1977: }
1978:
1979: public boolean isSignificant() {
1980: return false;
1981: }
1982:
1983: public String getPresentationName() {
1984: return "caret move";
1985: }
1986:
1987: public void undo() throws CannotUndoException {
1988: super .undo();
1989:
1990: select(start, end);
1991: }
1992:
1993: public void redo() throws CannotRedoException {
1994: super .redo();
1995:
1996: select(start, end);
1997: }
1998:
1999: public boolean addEdit(UndoableEdit edit) {
2000: if (edit instanceof CaretUndo) {
2001: CaretUndo cedit = (CaretUndo) edit;
2002: start = cedit.start;
2003: end = cedit.end;
2004: cedit.die();
2005:
2006: return true;
2007: } else
2008: return false;
2009: }
2010: }
2011:
2012: static {
2013: caretTimer = new Timer(500, new CaretBlinker());
2014: caretTimer.setInitialDelay(500);
2015: caretTimer.start();
2016: }
2017: }
|