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