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