/****************************************************************** * JADE - Java Agent DEvelopment Framework is a framework to develop * multi-agent systems in compliance with the FIPA specifications. * Copyright (C) 2002 TILAB S.p.A. * * This file is donated by Acklin B.V. to the JADE project. * * * GNU Lesser General Public License * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. * ***************************************************************/ package jade.tools.gui; import java.awt.*; import java.awt.Component; import java.awt.Toolkit; import java.awt.datatransfer.*; import java.awt.event.*; import java.awt.event.*; import java.awt.event.*; import java.awt.event.ActionListener; import java.awt.event.KeyListener; import java.lang.*; import java.lang.reflect.*; import java.util.EventObject; import java.util.Hashtable; import java.util.StringTokenizer; import javax.swing.*; import javax.swing.*; import javax.swing.JPopupMenu; import javax.swing.KeyStroke; import javax.swing.border.EtchedBorder; import javax.swing.event.*; import javax.swing.event.*; import javax.swing.text.*; import javax.swing.text.*; import jade.lang.acl.ACLMessage; import jade.tools.sl.SLFormatter; /** * jEdit's text area component. The original file is written by Slava Pestov * and altered to fit ACL/SL * * @author Slava Pestov * @created June 8, 2002 * @version $Id: ACLTextArea.java 5581 2005-02-23 08:59:06Z caire $ */ public class ACLTextArea extends JComponent { /** * Creates a new JEditTextArea with the default settings. */ public ACLTextArea() { // Enable the necessary events enableEvents(AWTEvent.KEY_EVENT_MASK); // Initialize some misc. stuff painter = new ACLTextAreaPainter(this); AutoScroll scroller = new AutoScroll(); scrollTimer = new Timer(200, scroller); documentHandler = new DocumentHandler(); listenerList = new EventListenerList(); caretEvent = new MutableCaretEvent(); lineSegment = new Segment(); bracketLine = bracketPosition = -1; blink = true; caretTimer = new Timer(500, new CaretBlinker()); caretTimer.setInitialDelay(500); caretTimer.start(); // Initialize the GUI setBorder(new EtchedBorder(EtchedBorder.LOWERED)); setLayout(new java.awt.BorderLayout()); add(painter, BorderLayout.CENTER); add(vertical = new JScrollBar(JScrollBar.VERTICAL), BorderLayout.EAST); add(horizontal = new JScrollBar(JScrollBar.HORIZONTAL), BorderLayout.SOUTH); // vertical.setVisibleAmount(20); // horizontal.setVisibleAmount(50); // Add some event listeners vertical.addAdjustmentListener(new AdjustHandler()); horizontal.addAdjustmentListener(new AdjustHandler()); painter.addComponentListener(new ComponentHandler()); painter.addMouseListener(new MouseHandler()); painter.addMouseMotionListener(scroller); addFocusListener(new FocusHandler()); // Load the defaults InputHandler DEFAULT_INPUT_HANDLER = new InputHandler(); DEFAULT_INPUT_HANDLER.addDefaultKeyBindings(); setInputHandler(DEFAULT_INPUT_HANDLER); setDocument(new ACLSyntaxDocument()); editable = true; caretVisible = true; caretBlinks = true; electricScroll = 3; // We don't seem to get the initial focus event? focusedComponent = this; //set tokenMarker setTokenMarker(new ACLSLTokenMarker()); } /** * Returns if this component can be traversed by pressing the Tab key. This * returns false. * * @return The ManagingFocus value */ public final boolean isManagingFocus() { return true; } /** * Returns the object responsible for painting this text area. * * @return The Painter value */ public final ACLTextAreaPainter getPainter() { return painter; } /** * Returns the input handler. * * @return The InputHandler value */ public final InputHandler getInputHandler() { return inputHandler; } /** * Returns true if the caret is blinking, false otherwise. * * @return The CaretBlinkEnabled value */ public final boolean isCaretBlinkEnabled() { return caretBlinks; } /** * Returns true if the caret is visible, false otherwise. * * @return The CaretVisible value */ public final boolean isCaretVisible() { return (!caretBlinks || blink) && caretVisible; } /** * Returns the number of lines from the top and button of the text area * that are always visible. * * @return The ElectricScroll value */ public final int getElectricScroll() { return electricScroll; } /** * Returns the line displayed at the text area's origin. * * @return The FirstLine value */ public final int getFirstLine() { return firstLine; } /** * Returns the number of lines visible in this text area. * * @return The VisibleLines value */ public final int getVisibleLines() { return visibleLines; } /** * Returns the horizontal offset of drawn lines. * * @return The HorizontalOffset value */ public final int getHorizontalOffset() { return horizontalOffset; } /** * Returns the document this text area is editing. * * @return The Document value */ public final ACLSyntaxDocument getDocument() { return document; } /** * Returns the document's token marker. Equivalent to calling <code>getDocument().getTokenMarker()</code> * . * * @return The TokenMarker value */ public final ACLSLTokenMarker getTokenMarker() { return document.getTokenMarker(); } /** * Returns the length of the document. Equivalent to calling <code>getDocument().getLength()</code> * . * * @return The DocumentLength value */ public final int getDocumentLength() { return document.getLength(); } /** * Returns the number of lines in the document. * * @return The LineCount value */ public final int getLineCount() { return document.getDefaultRootElement().getElementCount(); } /** * Returns the line containing the specified offset. * * @param offset The offset * @return The LineOfOffset value */ public final int getLineOfOffset(int offset) { return document.getDefaultRootElement().getElementIndex(offset); } /** * Returns the specified substring of the document. * * @param start The start offset * @param len The length of the substring * @return The substring, or null if the offsets are invalid */ public final String getText(int start, int len) { try { return document.getText(start, len); } catch (BadLocationException bl) { return null; } } /** * Copies the specified substring of the document into a segment. If the * offsets are invalid, the segment will contain a null string. * * @param start The start offset * @param len The length of the substring * @param segment The segment */ public final void getText(int start, int len, Segment segment) { try { document.getText(start, len, segment); } catch (BadLocationException bl) { segment.offset = segment.count = 0; } } /** * Returns the text on the specified line. * * @param lineIndex The line * @return The text, or null if the line is invalid */ public final String getLineText(int lineIndex) { int start = getLineStartOffset(lineIndex); return getText(start, getLineEndOffset(lineIndex) - start - 1); } /** * Copies the text on the specified line into a segment. If the line is * invalid, the segment will contain a null string. * * @param lineIndex The line * @param segment Description of Parameter */ public final void getLineText(int lineIndex, Segment segment) { int start = getLineStartOffset(lineIndex); getText(start, getLineEndOffset(lineIndex) - start - 1, segment); } /** * Returns the selection start offset. * * @return The SelectionStart value */ public final int getSelectionStart() { return selectionStart; } /** * Returns the selection start line. * * @return The SelectionStartLine value */ public final int getSelectionStartLine() { return selectionStartLine; } /** * Returns the selection end offset. * * @return The SelectionEnd value */ public final int getSelectionEnd() { return selectionEnd; } /** * Returns the selection end line. * * @return The SelectionEndLine value */ public final int getSelectionEndLine() { return selectionEndLine; } /** * Returns the caret position. This will either be the selection start or * the selection end, depending on which direction the selection was made * in. * * @return The CaretPosition value */ public final int getCaretPosition() { return (biasLeft ? selectionStart : selectionEnd); } /** * Returns the caret line. * * @return The CaretLine value */ public final int getCaretLine() { return (biasLeft ? selectionStartLine : selectionEndLine); } /** * Returns the mark position. This will be the opposite selection bound to * the caret position. * * @return The MarkPosition value * @see #getCaretPosition() */ public final int getMarkPosition() { return (biasLeft ? selectionEnd : selectionStart); } /** * Returns the mark line. * * @return The MarkLine value */ public final int getMarkLine() { return (biasLeft ? selectionEndLine : selectionStartLine); } /** * Returns the selected text, or null if no selection is active. * * @return The SelectedText value */ public final String getSelectedText() { if (selectionStart == selectionEnd) { return null; } return getText(selectionStart, selectionEnd - selectionStart); } /** * Returns true if this text area is editable, false otherwise. * * @return The Editable value */ public final boolean isEditable() { return editable; } /** * Returns the right click popup menu. * * @return The RightClickPopup value */ public final JPopupMenu getRightClickPopup() { return popup; } /** * Returns the `magic' caret position. This can be used to preserve the * column position when moving up and down lines. * * @return The MagicCaretPosition value */ public final int getMagicCaretPosition() { return magicCaret; } /** * Returns true if overwrite mode is enabled, false otherwise. * * @return The OverwriteEnabled value */ public final boolean isOverwriteEnabled() { return overwrite; } /** * Returns the position of the highlighted bracket (the bracket matching * the one before the caret) * * @return The BracketPosition value */ public final int getBracketPosition() { return bracketPosition; } /** * Returns the line of the highlighted bracket (the bracket matching the * one before the caret) * * @return The BracketLine value */ public final int getBracketLine() { return bracketLine; } /** * Sets the number of lines from the top and bottom of the text area that * are always visible * * @param electricScroll The number of lines always visible from the top * or bottom */ public final void setElectricScroll(int electricScroll) { this.electricScroll = electricScroll; } /** * Sets the document's token marker. Equivalent to caling <code>getDocument().setTokenMarker()</code> * . * * @param tokenMarker The token marker */ public final void setTokenMarker(ACLSLTokenMarker tokenMarker) { document.setTokenMarker(tokenMarker); } /** * Sets the selection start. The new selection will be the new selection * start and the old selection end. * * @param selectionStart The selection start * @see #select(int,int) */ public final void setSelectionStart(int selectionStart) { select(selectionStart, selectionEnd); } /** * Sets the selection end. The new selection will be the old selection * start and the bew selection end. * * @param selectionEnd The selection end * @see #select(int,int) */ public final void setSelectionEnd(int selectionEnd) { select(selectionStart, selectionEnd); } /** * Sets the caret position. The new selection will consist of the caret * position only (hence no text will be selected) * * @param caret The caret position * @see #select(int,int) */ public final void setCaretPosition(int caret) { select(caret, caret); } /** * Sets if this component is editable. * * @param editable True if this text area should be editable, false * otherwise */ public final void setEditable(boolean editable) { this.editable = editable; } /** * Sets the right click popup menu. * * @param popup The popup */ public final void setRightClickPopup(JPopupMenu popup) { this.popup = popup; } /** * Sets the `magic' caret position. This can be used to preserve the column * position when moving up and down lines. * * @param magicCaret The magic caret position */ public final void setMagicCaretPosition(int magicCaret) { this.magicCaret = magicCaret; } /** * Sets if overwrite mode should be enabled. * * @param overwrite True if overwrite mode should be enabled, false * otherwise. */ public final void setOverwriteEnabled(boolean overwrite) { this.overwrite = overwrite; painter.invalidateSelectedLines(); } /** * Blinks the caret. */ public final void blinkCaret() { if (caretBlinks) { blink = !blink; painter.invalidateSelectedLines(); } else { blink = true; } } /** * Recalculates the number of visible lines. This should not be called * directly. */ public final void recalculateVisibleLines() { if (painter == null) { return; } int height = painter.getHeight(); int lineHeight = painter.getFontMetrics().getHeight(); int oldVisibleLines = visibleLines; visibleLines = height / lineHeight; painter.invalidateOffscreen(); painter.repaint(); updateScrollBars(); } /** * Selects all text in the document. */ public final void selectAll() { select(0, getDocumentLength()); } /** * Adds a caret change listener to this text area. * * @param listener The listener */ public final void addCaretListener(CaretListener listener) { listenerList.add(CaretListener.class, listener); } /** * Removes a caret change listener from this text area. * * @param listener The listener */ public final void removeCaretListener(CaretListener listener) { listenerList.remove(CaretListener.class, listener); } /** * Returns the start offset of the specified line. * * @param line The line * @return The start offset of the specified line, or -1 if the line * is invalid */ public int getLineStartOffset(int line) { Element lineElement = document.getDefaultRootElement() .getElement(line); if (lineElement == null) { return -1; } else { return lineElement.getStartOffset(); } } /** * Returns the end offset of the specified line. * * @param line The line * @return The end offset of the specified line, or -1 if the line is * invalid. */ public int getLineEndOffset(int line) { Element lineElement = document.getDefaultRootElement() .getElement(line); if (lineElement == null) { return -1; } else { return lineElement.getEndOffset(); } } /** * Returns the length of the specified line. * * @param line The line * @return The LineLength value */ public int getLineLength(int line) { Element lineElement = document.getDefaultRootElement() .getElement(line); if (lineElement == null) { return -1; } else { return lineElement.getEndOffset() - lineElement.getStartOffset() - 1; } } /** * Returns the entire text of this text area. * * @return The Text value */ public String getText() { try { return document.getText(0, document.getLength()); } catch (BadLocationException bl) { return null; } } /** * Sets the input handler. * * @param inputHandler The new input handler */ public void setInputHandler(InputHandler inputHandler) { if (this.inputHandler != null) { removeKeyListener(this.inputHandler); } if (inputHandler != null) { addKeyListener(inputHandler); } this.inputHandler = inputHandler; } /** * Toggles caret blinking. * * @param caretBlinks True if the caret should blink, false otherwise */ public void setCaretBlinkEnabled(boolean caretBlinks) { this.caretBlinks = caretBlinks; if (!caretBlinks) { blink = false; } painter.invalidateSelectedLines(); } /** * Sets if the caret should be visible. * * @param caretVisible True if the caret should be visible, false * otherwise */ public void setCaretVisible(boolean caretVisible) { this.caretVisible = caretVisible; blink = true; painter.invalidateSelectedLines(); } /** * Sets the line displayed at the text area's origin without updating the * scroll bars. * * @param firstLine The new FirstLine value */ public void setFirstLine(int firstLine) { if (firstLine == this.firstLine) { return; } int oldFirstLine = this.firstLine; this.firstLine = firstLine; if (firstLine != vertical.getValue()) { updateScrollBars(); } painter.scrollRepaint(oldFirstLine, firstLine); painter.repaint(); } /** * Sets the horizontal offset of drawn lines. This can be used to implement * horizontal scrolling. * * @param horizontalOffset offset The new horizontal offset */ public void setHorizontalOffset(int horizontalOffset) { if (horizontalOffset == this.horizontalOffset) { return; } this.horizontalOffset = horizontalOffset; if (horizontalOffset != horizontal.getValue()) { updateScrollBars(); } painter.invalidateLineRange(firstLine, firstLine + visibleLines); painter.repaint(); } /** * A fast way of changing both the first line and horizontal offset. * * @param firstLine The new first line * @param horizontalOffset The new horizontal offset * @return True if any of the values were changed, false * otherwise */ public boolean setOrigin(int firstLine, int horizontalOffset) { boolean changed = false; boolean fullRepaint = false; int oldFirstLine = this.firstLine; if (horizontalOffset != this.horizontalOffset) { this.horizontalOffset = horizontalOffset; changed = fullRepaint = true; } if (firstLine != this.firstLine) { this.firstLine = firstLine; changed = true; } if (changed) { updateScrollBars(); if (fullRepaint) { painter._invalidateLineRange(firstLine, firstLine + visibleLines); } else { painter.scrollRepaint(oldFirstLine, firstLine); } painter.repaint(); } return changed; } /** * Sets the document this text area is editing. * * @param document The document */ public void setDocument(ACLSyntaxDocument document) { if (this.document == document) { return; } if (this.document != null) { this.document.removeDocumentListener(documentHandler); } this.document = document; document.addDocumentListener(documentHandler); select(0, 0); updateScrollBars(); painter.invalidateOffscreen(); painter.repaint(); } /** * Sets the entire text of this text area. * * @param text The new Text value */ public void setText(String text) { try { document.remove(0, document.getLength()); document.insertString(0, text, null); } catch (BadLocationException bl) { bl.printStackTrace(); } } /** * Replaces the selection with the specified text. * * @param selectedText The replacement text for the selection */ public void setSelectedText(String selectedText) { if (!editable) { throw new InternalError("Text component" + " read only"); } try { document.remove(selectionStart, selectionEnd - selectionStart); document.insertString(selectionStart, selectedText, null); setCaretPosition(selectionEnd); } catch (BadLocationException bl) { bl.printStackTrace(); throw new InternalError("Cannot replace" + " selection"); } } public void update() { this.register(msg, "Content"); } /** * Description of the Method * * @param arg Description of Parameter * @param fieldName Description of Parameter */ public void register(Object arg, String fieldName) { this.msg = (ACLMessage)arg; this.fieldName = fieldName; contentLanguage = (msg.getLanguage() != null ? msg.getLanguage() : "<unknown>"); String methodName = "get" + fieldName; String content = ""; try { Method sn = msg.getClass().getMethod(methodName, (Class[]) null); content = (String)sn.invoke(msg, new Object[]{}); } catch (Exception ex) { ex.printStackTrace(); } if (contentLanguage.indexOf("SL") >= 0) { //Only format when SL try { content = (String)new SLFormatter().format(content); } catch (Exception ex) { //too bad! } } while ((content != null) && (content.indexOf('\n')) == 0) { content = content.substring(1); } setText(content); this.setCaretPosition(0); } /** * Description of the Method * * @param arg Description of Parameter * @param str Description of Parameter */ public void unregister(Object arg, String str) { // msg.deleteObserver(this); } /** * Description of the Method * * @param e Description of Parameter */ public void focusLost(FocusEvent e) { String value = getText(); while ((value != null) && ((value.indexOf('\n') == 0) || (value.indexOf(' ') == 0))) { value = value.substring(1); } String methodName = "set" + fieldName; String theType = "java.lang.String"; try { Method sn = msg.getClass().getMethod(methodName, new Class[]{Class.forName(theType)}); Object os = value; sn.invoke(msg, new Object[]{os}); } catch (Exception ex) { ex.printStackTrace(); } } /** * Updates the state of the scroll bars. This should be called if the * number of lines in the document changes, or when the size of the text * are changes. */ public void updateScrollBars() { if (vertical != null && visibleLines != 0) { vertical.setValues(firstLine, visibleLines, 0, getLineCount()); vertical.setUnitIncrement(2); vertical.setBlockIncrement(visibleLines); } int width = painter.getWidth(); if (horizontal != null && width != 0) { horizontal.setValues(-horizontalOffset, width, 0, width * 5); horizontal.setUnitIncrement(painter.getFontMetrics() .charWidth('w')); horizontal.setBlockIncrement(width / 2); } } /** * Ensures that the caret is visible by scrolling the text area if * necessary. * * @return True if scrolling was actually performed, false if the caret * was already visible */ public boolean scrollToCaret() { int line = getCaretLine(); int lineStart = getLineStartOffset(line); int offset = Math.max(0, Math.min(getLineLength(line) - 1, getCaretPosition() - lineStart)); return scrollTo(line, offset); } /** * Ensures that the specified line and offset is visible by scrolling the * text area if necessary. * * @param line The line to scroll to * @param offset The offset in the line to scroll to * @return True if scrolling was actually performed, false if the * line and offset was already visible */ public boolean scrollTo(int line, int offset) { // visibleLines == 0 before the component is realized // we can't do any proper scrolling then, so we have // this hack... if (visibleLines == 0) { setFirstLine(Math.max(0, line - electricScroll)); return true; } int newFirstLine = firstLine; int newHorizontalOffset = horizontalOffset; if (line < firstLine + electricScroll) { newFirstLine = Math.max(0, line - electricScroll); } else if (line + electricScroll >= firstLine + visibleLines) { newFirstLine = (line - visibleLines) + electricScroll + 1; if (newFirstLine + visibleLines >= getLineCount()) { newFirstLine = getLineCount() - visibleLines; } if (newFirstLine < 0) { newFirstLine = 0; } } int x = offsetToX(line, offset); int width = painter.getFontMetrics().charWidth('W'); if (x < 0) { newHorizontalOffset = Math.min(0, horizontalOffset - x + width); } else if (x + width >= painter.getWidth()) { newHorizontalOffset = horizontalOffset + (painter.getWidth() - x) - width; } return setOrigin(newFirstLine, newHorizontalOffset); } /** * Converts a line index to a y co-ordinate. * * @param line The line * @return Description of the Returned Value */ public int lineToY(int line) { FontMetrics fm = painter.getFontMetrics(); return (line - firstLine) * fm.getHeight() - (fm.getLeading() + fm.getMaxDescent()); } /** * Converts a y co-ordinate to a line index. * * @param y The y co-ordinate * @return Description of the Returned Value */ public int yToLine(int y) { FontMetrics fm = painter.getFontMetrics(); int height = fm.getHeight(); return Math.max(0, Math.min(getLineCount() - 1, y / height + firstLine)); } /** * Converts an offset in a line into an x co-ordinate. * * @param line The line * @param offset The offset, from the start of the line * @return Description of the Returned Value */ public int offsetToX(int line, int offset) { ACLSLTokenMarker tokenMarker = getTokenMarker(); /* Use painter's cached info for speed */ FontMetrics fm = painter.getFontMetrics(); getLineText(line, lineSegment); int segmentOffset = lineSegment.offset; int x = horizontalOffset; /* If syntax coloring is disabled, do simple translation */ if (tokenMarker == null) { lineSegment.count = offset; return x + Utilities.getTabbedTextWidth(lineSegment, fm, x, painter, 0); } /* If syntax coloring is enabled, we have to do this because tokens can vary in width */ else { ACLToken tokens; if (painter.currentLineIndex == line) { tokens = painter.currentLineTokens; } else { painter.currentLineIndex = line; tokens = painter.currentLineTokens = tokenMarker.markTokens(lineSegment, line); } Toolkit toolkit = painter.getToolkit(); Font defaultFont = painter.getFont(); ACLSytntaxStyle[] styles = painter.getStyles(); for (; ; ) { byte id = tokens.id; if (id == ACLToken.END) { return x; } if (id == ACLToken.NULL) { fm = painter.getFontMetrics(); } else { fm = styles[id].getFontMetrics(defaultFont); } int length = tokens.length; if (offset + segmentOffset < lineSegment.offset + length) { lineSegment.count = offset - (lineSegment.offset - segmentOffset); return x + Utilities.getTabbedTextWidth( lineSegment, fm, x, painter, 0); } else { lineSegment.count = length; x += Utilities.getTabbedTextWidth( lineSegment, fm, x, painter, 0); lineSegment.offset += length; } tokens = tokens.next; } } } /** * Converts an x co-ordinate to an offset within a line. * * @param line The line * @param x The x co-ordinate * @return Description of the Returned Value */ public int xToOffset(int line, int x) { ACLSLTokenMarker tokenMarker = getTokenMarker(); /* Use painter's cached info for speed */ FontMetrics fm = painter.getFontMetrics(); getLineText(line, lineSegment); char[] segmentArray = lineSegment.array; int segmentOffset = lineSegment.offset; int segmentCount = lineSegment.count; int width = horizontalOffset; if (tokenMarker == null) { for (int i = 0; i < segmentCount; i++) { char c = segmentArray[i + segmentOffset]; int charWidth; if (c == '\t') { charWidth = (int)painter.nextTabStop(width, i) - width; } else { charWidth = fm.charWidth(c); } if (painter.isBlockCaretEnabled()) { if (x - charWidth <= width) { return i; } else if (x - charWidth / 2 <= width) { return i; } } width += charWidth; } return segmentCount; } else { ACLToken tokens; if (painter.currentLineIndex == line) { tokens = painter.currentLineTokens; } else { painter.currentLineIndex = line; tokens = painter.currentLineTokens = tokenMarker.markTokens(lineSegment, line); } int offset = 0; Toolkit toolkit = painter.getToolkit(); Font defaultFont = painter.getFont(); ACLSytntaxStyle[] styles = painter.getStyles(); for (; ; ) { byte id = tokens.id; if (id == ACLToken.END) { return offset; } if (id == ACLToken.NULL) { fm = painter.getFontMetrics(); } else { fm = styles[id].getFontMetrics(defaultFont); } int length = tokens.length; for (int i = 0; i < length; i++) { char c = segmentArray[segmentOffset + offset + i]; int charWidth; if (c == '\t') { charWidth = (int)painter.nextTabStop(width, offset + i) - width; } else { charWidth = fm.charWidth(c); } if (painter.isBlockCaretEnabled()) { if (x - charWidth <= width) { return offset + i; } else if (x - charWidth / 2 <= width) { return offset + i; } } width += charWidth; } offset += length; tokens = tokens.next; } } } /** * Converts a point to an offset, from the start of the text. * * @param x The x co-ordinate of the point * @param y The y co-ordinate of the point * @return Description of the Returned Value */ public int xyToOffset(int x, int y) { int line = yToLine(y); int start = getLineStartOffset(line); return start + xToOffset(line, x); } /** * Selects from the start offset to the end offset. This is the general * selection method used by all other selecting methods. The caret position * will be start if start < end, and end if end > start. * * @param start The start offset * @param end The end offset */ public void select(int start, int end) { int newStart; int newEnd; boolean newBias; if (start <= end) { newStart = start; newEnd = end; newBias = false; } else { newStart = end; newEnd = start; newBias = true; } if (newStart < 0 || newEnd > getDocumentLength()) { throw new IllegalArgumentException("Bounds out of" + " range: " + newStart + "," + newEnd); } // If the new position is the same as the old, we don't // do all this crap, however we still do the stuff at // the end (clearing magic position, scrolling) if (newStart != selectionStart || newEnd != selectionEnd || newBias != biasLeft) { int newStartLine = getLineOfOffset(newStart); int newEndLine = getLineOfOffset(newEnd); if (painter.isBracketHighlightEnabled()) { if (bracketLine != -1) { painter._invalidateLine(bracketLine); } updateBracketHighlight(end); if (bracketLine != -1) { painter._invalidateLine(bracketLine); } } painter._invalidateLineRange(selectionStartLine, selectionEndLine); painter._invalidateLineRange(newStartLine, newEndLine); selectionStart = newStart; selectionEnd = newEnd; selectionStartLine = newStartLine; selectionEndLine = newEndLine; biasLeft = newBias; fireCaretEvent(); } // When the user is typing, etc, we don't want the caret // to blink blink = true; caretTimer.restart(); // Clear the `magic' caret position used by up/down magicCaret = -1; if (!scrollToCaret()) { painter.fastRepaint(); } } /** * Similar to <code>setSelectedText()</code>, but overstrikes the * appropriate number of characters if overwrite mode is enabled. * * @param str The string * @see #setSelectedText(String) * @see #isOverwriteEnabled() */ public void overwriteSetSelectedText(String str) { // Don't overstrike if there is a selection if (!overwrite || selectionStart != selectionEnd) { setSelectedText(str); return; } // Don't overstrike if we're on the end of // the line int caret = getCaretPosition(); int caretLineEnd = getLineEndOffset(getCaretLine()); if (caretLineEnd - caret <= str.length()) { setSelectedText(str); return; } try { document.remove(caret, str.length()); document.insertString(caret, str, null); } catch (BadLocationException bl) { bl.printStackTrace(); } } /** * Deletes the selected text from the text area and places it into the * clipboard. */ public void cut() { if (editable) { copy(); setSelectedText(""); } } /** * Places the selected text into the clipboard. */ public void copy() { if (selectionStart != selectionEnd) { Clipboard clipboard = getToolkit().getSystemClipboard(); StringSelection selection = new StringSelection( getSelectedText()); clipboard.setContents(selection, null); } } /** * Inserts the clipboard contents into the text. */ public void paste() { if (editable) { Clipboard clipboard = getToolkit().getSystemClipboard(); try { String selection = (String)(clipboard.getContents(this).getTransferData( DataFlavor.stringFlavor)); // The MacOS MRJ doesn't convert \r to \n, // so do it here setSelectedText(selection.replace('\r', '\n')); } catch (Exception e) { getToolkit().beep(); System.err.println("Clipboard does not" + " contain a string"); } } } /** * Called by the AWT when this component is removed from it's parent. This * stops any autoscrolling and clears the currently focused component. */ public void removeNotify() { super.removeNotify(); if (focusedComponent == this) { focusedComponent = null; } if (scrollTimer.isRunning()) { scrollTimer.stop(); } } /** * Description of the Method * * @param e Description of Parameter */ protected void processFocusEvent(FocusEvent e) { super.processFocusEvent(e); if (e.getID() == e.FOCUS_LOST) { focusLost(e); } } protected void fireCaretEvent() { Object[] listeners = listenerList.getListenerList(); for (int i = listeners.length - 2; i >= 0; i--) { if (listeners[i] == CaretListener.class) { ((CaretListener)listeners[i + 1]).caretUpdate(caretEvent); } } } protected void updateBracketHighlight(int newCaretPosition) { if (newCaretPosition == 0) { bracketPosition = bracketLine = -1; return; } try { int offset = TextUtilities.findMatchingBracket( document, newCaretPosition - 1); if (offset != -1) { bracketLine = getLineOfOffset(offset); bracketPosition = offset - getLineStartOffset(bracketLine); return; } } catch (BadLocationException bl) { bl.printStackTrace(); } bracketLine = bracketPosition = -1; } protected void documentChanged(DocumentEvent evt) { DocumentEvent.ElementChange ch = evt.getChange( document.getDefaultRootElement()); int count; if (ch == null) { count = 0; } else { count = ch.getChildrenAdded().length - ch.getChildrenRemoved().length; } if (count == 0) { int line = getLineOfOffset(evt.getOffset()); painter._invalidateLine(line); } else { int index = ch.getIndex(); painter._invalidateLineRange(index, Math.max(getLineCount(), firstLine + visibleLines)); updateScrollBars(); } } public static class TextUtilities { /** * Returns the offset of the bracket matching the one at the specified * offset of the document, or -1 if the bracket is unmatched (or if the * character is not a bracket). * * @param doc The document * @param offset The offset * @return Description of the Returned Value * @exception BadLocationException If an out-of-bounds access was * attempted on the document text */ public static int findMatchingBracket(Document doc, int offset) throws BadLocationException { if (doc.getLength() == 0) { return -1; } char c = doc.getText(offset, 1).charAt(0); char cprime;// c` - corresponding character boolean direction;// true = back, false = forward switch (c) { case '(': cprime = ')'; direction = false; break; case ')': cprime = '('; direction = true; break; case '[': cprime = ']'; direction = false; break; case ']': cprime = '['; direction = true; break; case '{': cprime = '}'; direction = false; break; case '}': cprime = '{'; direction = true; break; default: return -1; } int count; // How to merge these two cases is left as an exercise // for the reader. // Go back or forward if (direction) { // Count is 1 initially because we have already // `found' one closing bracket count = 1; // Get text[0,offset-1]; String text = doc.getText(0, offset); // Scan backwards for (int i = offset - 1; i >= 0; i--) { // If text[i] == c, we have found another // closing bracket, therefore we will need // two opening brackets to complete the // match. char x = text.charAt(i); if (x == c) { count++; } // If text[i] == cprime, we have found a // opening bracket, so we return i if // --count == 0 else if (x == cprime) { if (--count == 0) { return i; } } } } else { // Count is 1 initially because we have already // `found' one opening bracket count = 1; // So we don't have to + 1 in every loop offset++; // Number of characters to check int len = doc.getLength() - offset; // Get text[offset+1,len]; String text = doc.getText(offset, len); // Scan forwards for (int i = 0; i < len; i++) { // If text[i] == c, we have found another // opening bracket, therefore we will need // two closing brackets to complete the // match. char x = text.charAt(i); if (x == c) { count++; } // If text[i] == cprime, we have found an // closing bracket, so we return i if // --count == 0 else if (x == cprime) { if (--count == 0) { return i + offset; } } } } // Nothing found return -1; } } static class CaretBlinker implements ActionListener { public void actionPerformed(ActionEvent evt) { if (focusedComponent != null && focusedComponent.hasFocus()) { focusedComponent.blinkCaret(); } } } private static class InputHandler implements KeyListener { /** * Creates a new input handler with no key bindings defined. */ public InputHandler() { bindings = currentBindings = new Hashtable(); } public static ACLTextArea getTextArea(EventObject evt) { if (evt != null) { Object o = evt.getSource(); if (o instanceof Component) { // find the parent text area Component c = (Component)o; for (; ; ) { if (c instanceof ACLTextArea) { return (ACLTextArea)c; } else if (c == null) { break; } if (c instanceof JPopupMenu) { c = ((JPopupMenu)c) .getInvoker(); } else { c = c.getParent(); } } } } // this shouldn't happen System.err.println("BUG: getTextArea() returning null"); System.err.println("Report this to Slava Pestov <sp@gjt.org>"); return null; } /** * Converts a string to a keystroke. The string should be of the form <i> * modifiers</i> +<i>shortcut</i> where <i>modifiers</i> is any * combination of A for Alt, C for Control, S for Shift or M for Meta, * and <i>shortcut </i> is either a single character, or a keycode name * from the <code>KeyEvent</code> class, without the <code>VK_</code> * prefix. * * @param keyStroke A string description of the key stroke * @return Description of the Returned Value */ public static KeyStroke parseKeyStroke(String keyStroke) { if (keyStroke == null) { return null; } int modifiers = 0; int ch = '\0'; int index = keyStroke.indexOf('+'); if (index != -1) { for (int i = 0; i < index; i++) { switch (Character.toUpperCase(keyStroke.charAt(i))) { case 'A': modifiers |= InputEvent.ALT_MASK; break; case 'C': modifiers |= InputEvent.CTRL_MASK; break; case 'M': modifiers |= InputEvent.META_MASK; break; case 'S': modifiers |= InputEvent.SHIFT_MASK; break; } } } String key = keyStroke.substring(index + 1); if (key.length() == 1) { ch = Character.toUpperCase(key.charAt(0)); } else if (key.length() == 0) { System.err.println("Invalid key stroke: " + keyStroke); return null; } else { try { ch = KeyEvent.class.getField("VK_".concat(key)) .getInt(null); } catch (Exception e) { System.err.println("Invalid key stroke: " + keyStroke); return null; } } return KeyStroke.getKeyStroke(ch, modifiers); } /** * Sets up the default key bindings. */ public void addDefaultKeyBindings() { addKeyBinding("BACK_SPACE", BACKSPACE); addKeyBinding("DELETE", DELETE); addKeyBinding("ENTER", INSERT_BREAK); addKeyBinding("TAB", INSERT_TAB); addKeyBinding("INSERT", OVERWRITE); addKeyBinding("HOME", HOME); addKeyBinding("END", END); addKeyBinding("S+HOME", SELECT_HOME); addKeyBinding("S+END", SELECT_END); addKeyBinding("PAGE_UP", PREV_PAGE); addKeyBinding("PAGE_DOWN", NEXT_PAGE); addKeyBinding("S+PAGE_UP", SELECT_PREV_PAGE); addKeyBinding("S+PAGE_DOWN", SELECT_NEXT_PAGE); addKeyBinding("LEFT", PREV_CHAR); addKeyBinding("S+LEFT", SELECT_PREV_CHAR); addKeyBinding("C+LEFT", PREV_WORD); addKeyBinding("CS+LEFT", SELECT_PREV_WORD); addKeyBinding("RIGHT", NEXT_CHAR); addKeyBinding("S+RIGHT", SELECT_NEXT_CHAR); addKeyBinding("C+RIGHT", NEXT_WORD); addKeyBinding("CS+RIGHT", SELECT_NEXT_WORD); addKeyBinding("UP", PREV_LINE); addKeyBinding("S+UP", SELECT_PREV_LINE); addKeyBinding("DOWN", NEXT_LINE); addKeyBinding("S+DOWN", SELECT_NEXT_LINE); } /** * Adds a key binding to this input handler. The key binding is a list of * white space separated key strokes of the form <i>[modifiers+]key</i> * where modifier is C for Control, A for Alt, or S for Shift, and key is * either a character (a-z) or a field name in the KeyEvent class * prefixed with VK_ (e.g., BACK_SPACE) * * @param keyBinding The key binding * @param action The action */ public void addKeyBinding(String keyBinding, ActionListener action) { Hashtable current = bindings; StringTokenizer st = new StringTokenizer(keyBinding); while (st.hasMoreTokens()) { KeyStroke keyStroke = parseKeyStroke(st.nextToken()); if (keyStroke == null) { return; } if (st.hasMoreTokens()) { Object o = current.get(keyStroke); if (o instanceof Hashtable) { current = (Hashtable)o; } else { o = new Hashtable(); current.put(keyStroke, o); current = (Hashtable)o; } } else { current.put(keyStroke, action); } } } /** * Removes a key binding from this input handler. This is not yet * implemented. * * @param keyBinding The key binding */ public void removeKeyBinding(String keyBinding) { throw new InternalError("Not yet implemented"); } /** * Removes all key bindings from this input handler. */ public void removeAllKeyBindings() { bindings.clear(); } /** * Handle a key pressed event. This will look up the binding for the key * stroke and execute it. * * @param evt Description of Parameter */ public void keyPressed(KeyEvent evt) { int keyCode = evt.getKeyCode(); int modifiers = evt.getModifiers(); if ((modifiers & ~KeyEvent.SHIFT_MASK) != 0 || evt.isActionKey() || keyCode == KeyEvent.VK_BACK_SPACE || keyCode == KeyEvent.VK_DELETE || keyCode == KeyEvent.VK_ENTER || keyCode == KeyEvent.VK_TAB) { KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, modifiers); Object o = currentBindings.get(keyStroke); if (o == null) { // Don't beep if the user presses some // key we don't know about unless a // prefix is active. Otherwise it will // beep when caps lock is pressed, etc. if (currentBindings != bindings) { Toolkit.getDefaultToolkit().beep(); // F10 should be passed on, but C+e F10 // shouldn't evt.consume(); } currentBindings = bindings; return; } else if (o instanceof ActionListener) { ((ActionListener)o).actionPerformed( new ActionEvent(evt.getSource(), ActionEvent.ACTION_PERFORMED, null, modifiers)); currentBindings = bindings; evt.consume(); return; } else if (o instanceof Hashtable) { currentBindings = (Hashtable)o; evt.consume(); return; } else if (keyCode != KeyEvent.VK_ALT && keyCode != KeyEvent.VK_CONTROL && keyCode != KeyEvent.VK_SHIFT && keyCode != KeyEvent.VK_META) { return; } } } /** * Handle a key released event. These are ignored. * * @param evt Description of Parameter */ public void keyReleased(KeyEvent evt) { } /** * Handle a key typed event. This inserts the key into the text area. * * @param evt Description of Parameter */ public void keyTyped(KeyEvent evt) { int modifiers = evt.getModifiers(); char c = evt.getKeyChar(); if (c != KeyEvent.CHAR_UNDEFINED && (modifiers & KeyEvent.ALT_MASK) == 0) { if (c >= 0x20 && c != 0x7f) { ACLTextArea textArea = getTextArea(evt); if (!textArea.isEditable()) { textArea.getToolkit().beep(); return; } currentBindings = bindings; textArea.overwriteSetSelectedText(String.valueOf(c)); } } } public static class backspace implements ActionListener { public void actionPerformed(ActionEvent evt) { ACLTextArea textArea = getTextArea(evt); if (!textArea.isEditable()) { textArea.getToolkit().beep(); return; } if (textArea.getSelectionStart() != textArea.getSelectionEnd()) { textArea.setSelectedText(""); } else { int caret = textArea.getCaretPosition(); if (caret == 0) { textArea.getToolkit().beep(); return; } try { textArea.getDocument().remove(caret - 1, 1); } catch (BadLocationException bl) { bl.printStackTrace(); } } } } public static class delete implements ActionListener { public void actionPerformed(ActionEvent evt) { ACLTextArea textArea = getTextArea(evt); if (!textArea.isEditable()) { textArea.getToolkit().beep(); return; } if (textArea.getSelectionStart() != textArea.getSelectionEnd()) { textArea.setSelectedText(""); } else { int caret = textArea.getCaretPosition(); if (caret == textArea.getDocumentLength()) { textArea.getToolkit().beep(); return; } try { textArea.getDocument().remove(caret, 1); } catch (BadLocationException bl) { bl.printStackTrace(); } } } } public static class end implements ActionListener { public end(boolean select) { this.select = select; } public void actionPerformed(ActionEvent evt) { ACLTextArea textArea = getTextArea(evt); int caret = textArea.getCaretPosition(); int lastOfLine = textArea.getLineEndOffset( textArea.getCaretLine()) - 1; int lastVisibleLine = textArea.getFirstLine() + textArea.getVisibleLines(); if (lastVisibleLine >= textArea.getLineCount()) { lastVisibleLine = Math.min(textArea.getLineCount() - 1, lastVisibleLine); } else { lastVisibleLine -= (textArea.getElectricScroll() + 1); } int lastVisible = textArea.getLineEndOffset(lastVisibleLine) - 1; int lastDocument = textArea.getDocumentLength(); if (caret == lastDocument) { textArea.getToolkit().beep(); return; } else if (caret == lastVisible) { caret = lastDocument; } else if (caret == lastOfLine) { caret = lastVisible; } else { caret = lastOfLine; } if (select) { textArea.select(textArea.getMarkPosition(), caret); } else { textArea.setCaretPosition(caret); } } private boolean select; } public static class home implements ActionListener { public home(boolean select) { this.select = select; } public void actionPerformed(ActionEvent evt) { ACLTextArea textArea = getTextArea(evt); int caret = textArea.getCaretPosition(); int firstLine = textArea.getFirstLine(); int firstOfLine = textArea.getLineStartOffset( textArea.getCaretLine()); int firstVisibleLine = (firstLine == 0 ? 0 : firstLine + textArea.getElectricScroll()); int firstVisible = textArea.getLineStartOffset( firstVisibleLine); if (caret == 0) { textArea.getToolkit().beep(); return; } else if (caret == firstVisible) { caret = 0; } else if (caret == firstOfLine) { caret = firstVisible; } else { caret = firstOfLine; } if (select) { textArea.select(textArea.getMarkPosition(), caret); } else { textArea.setCaretPosition(caret); } } private boolean select; } public static class insert_break implements ActionListener { public void actionPerformed(ActionEvent evt) { ACLTextArea textArea = getTextArea(evt); if (!textArea.isEditable()) { textArea.getToolkit().beep(); return; } textArea.setSelectedText("\n"); } } public static class insert_tab implements ActionListener { public void actionPerformed(ActionEvent evt) { ACLTextArea textArea = getTextArea(evt); if (!textArea.isEditable()) { textArea.getToolkit().beep(); return; } textArea.overwriteSetSelectedText("\t"); } } public static class next_char implements ActionListener { public next_char(boolean select) { this.select = select; } public void actionPerformed(ActionEvent evt) { ACLTextArea textArea = getTextArea(evt); int caret = textArea.getCaretPosition(); if (caret == textArea.getDocumentLength()) { textArea.getToolkit().beep(); return; } if (select) { textArea.select(textArea.getMarkPosition(), caret + 1); } else { textArea.setCaretPosition(caret + 1); } } private boolean select; } public static class next_line implements ActionListener { public next_line(boolean select) { this.select = select; } public void actionPerformed(ActionEvent evt) { ACLTextArea textArea = getTextArea(evt); int caret = textArea.getCaretPosition(); int line = textArea.getCaretLine(); if (line == textArea.getLineCount() - 1) { textArea.getToolkit().beep(); return; } int magic = textArea.getMagicCaretPosition(); if (magic == -1) { magic = textArea.offsetToX(line, caret - textArea.getLineStartOffset(line)); } caret = textArea.getLineStartOffset(line + 1) + textArea.xToOffset(line + 1, magic); if (select) { textArea.select(textArea.getMarkPosition(), caret); } else { textArea.setCaretPosition(caret); } textArea.setMagicCaretPosition(magic); } private boolean select; } public static class next_page implements ActionListener { public next_page(boolean select) { this.select = select; } public void actionPerformed(ActionEvent evt) { ACLTextArea textArea = getTextArea(evt); int lineCount = textArea.getLineCount(); int firstLine = textArea.getFirstLine(); int visibleLines = textArea.getVisibleLines(); int line = textArea.getCaretLine(); firstLine += visibleLines; if (firstLine + visibleLines >= lineCount - 1) { firstLine = lineCount - visibleLines; } textArea.setFirstLine(firstLine); int caret = textArea.getLineStartOffset( Math.min(textArea.getLineCount() - 1, line + visibleLines)); if (select) { textArea.select(textArea.getMarkPosition(), caret); } else { textArea.setCaretPosition(caret); } } private boolean select; } public static class next_word implements ActionListener { public next_word(boolean select) { this.select = select; } public void actionPerformed(ActionEvent evt) { ACLTextArea textArea = getTextArea(evt); int caret = textArea.getCaretPosition(); int line = textArea.getCaretLine(); int lineStart = textArea.getLineStartOffset(line); caret -= lineStart; String lineText = textArea.getLineText(textArea.getCaretLine()); if (caret == lineText.length()) { if (lineStart + caret == textArea.getDocumentLength()) { textArea.getToolkit().beep(); return; } caret++; } else { char ch = lineText.charAt(caret); String noWordSep = (String)textArea.getDocument() .getProperty("noWordSep"); boolean selectNoLetter = (!Character.isLetterOrDigit(ch) && noWordSep.indexOf(ch) == -1); int wordEnd = lineText.length(); for (int i = caret; i < lineText.length(); i++) { ch = lineText.charAt(i); if (selectNoLetter ^ (!Character.isLetterOrDigit(ch) && noWordSep.indexOf(ch) == -1)) { wordEnd = i; break; } } caret = wordEnd; } if (select) { textArea.select(textArea.getMarkPosition(), lineStart + caret); } else { textArea.setCaretPosition(lineStart + caret); } } private boolean select; } public static class overwrite implements ActionListener { public void actionPerformed(ActionEvent evt) { ACLTextArea textArea = getTextArea(evt); textArea.setOverwriteEnabled( !textArea.isOverwriteEnabled()); } } public static class prev_char implements ActionListener { public prev_char(boolean select) { this.select = select; } public void actionPerformed(ActionEvent evt) { ACLTextArea textArea = getTextArea(evt); int caret = textArea.getCaretPosition(); if (caret == 0) { textArea.getToolkit().beep(); return; } if (select) { textArea.select(textArea.getMarkPosition(), caret - 1); } else { textArea.setCaretPosition(caret - 1); } } private boolean select; } public static class prev_line implements ActionListener { public prev_line(boolean select) { this.select = select; } public void actionPerformed(ActionEvent evt) { ACLTextArea textArea = getTextArea(evt); int caret = textArea.getCaretPosition(); int line = textArea.getCaretLine(); if (line == 0) { textArea.getToolkit().beep(); return; } int magic = textArea.getMagicCaretPosition(); if (magic == -1) { magic = textArea.offsetToX(line, caret - textArea.getLineStartOffset(line)); } caret = textArea.getLineStartOffset(line - 1) + textArea.xToOffset(line - 1, magic); if (select) { textArea.select(textArea.getMarkPosition(), caret); } else { textArea.setCaretPosition(caret); } textArea.setMagicCaretPosition(magic); } private boolean select; } public static class prev_page implements ActionListener { public prev_page(boolean select) { this.select = select; } public void actionPerformed(ActionEvent evt) { ACLTextArea textArea = getTextArea(evt); int firstLine = textArea.getFirstLine(); int visibleLines = textArea.getVisibleLines(); int line = textArea.getCaretLine(); if (firstLine < visibleLines) { firstLine = visibleLines; } textArea.setFirstLine(firstLine - visibleLines); int caret = textArea.getLineStartOffset( Math.max(0, line - visibleLines)); if (select) { textArea.select(textArea.getMarkPosition(), caret); } else { textArea.setCaretPosition(caret); } } private boolean select; } public static class prev_word implements ActionListener { public prev_word(boolean select) { this.select = select; } public void actionPerformed(ActionEvent evt) { ACLTextArea textArea = getTextArea(evt); int caret = textArea.getCaretPosition(); int line = textArea.getCaretLine(); int lineStart = textArea.getLineStartOffset(line); caret -= lineStart; String lineText = textArea.getLineText(textArea.getCaretLine()); if (caret == 0) { if (lineStart == 0) { textArea.getToolkit().beep(); return; } caret--; } else { char ch = lineText.charAt(caret - 1); String noWordSep = (String)textArea.getDocument() .getProperty("noWordSep"); boolean selectNoLetter = (!Character.isLetterOrDigit(ch) && noWordSep.indexOf(ch) == -1); int wordStart = 0; for (int i = caret - 1; i >= 0; i--) { ch = lineText.charAt(i); if (selectNoLetter ^ (!Character.isLetterOrDigit(ch) && noWordSep.indexOf(ch) == -1)) { wordStart = i + 1; break; } } caret = wordStart; } if (select) { textArea.select(textArea.getMarkPosition(), lineStart + caret); } else { textArea.setCaretPosition(lineStart + caret); } } private boolean select; } public final static ActionListener BACKSPACE = new backspace(); public final static ActionListener DELETE = new delete(); public final static ActionListener END = new end(false); public final static ActionListener SELECT_END = new end(true); public final static ActionListener INSERT_BREAK = new insert_break(); public final static ActionListener INSERT_TAB = new insert_tab(); public final static ActionListener HOME = new home(false); public final static ActionListener SELECT_HOME = new home(true); public final static ActionListener NEXT_CHAR = new next_char(false); public final static ActionListener NEXT_LINE = new next_line(false); public final static ActionListener NEXT_PAGE = new next_page(false); public final static ActionListener NEXT_WORD = new next_word(false); public final static ActionListener SELECT_NEXT_CHAR = new next_char(true); public final static ActionListener SELECT_NEXT_LINE = new next_line(true); public final static ActionListener SELECT_NEXT_PAGE = new next_page(true); public final static ActionListener SELECT_NEXT_WORD = new next_word(true); public final static ActionListener OVERWRITE = new overwrite(); public final static ActionListener PREV_CHAR = new prev_char(false); public final static ActionListener PREV_LINE = new prev_line(false); public final static ActionListener PREV_PAGE = new prev_page(false); public final static ActionListener PREV_WORD = new prev_word(false); public final static ActionListener SELECT_PREV_CHAR = new prev_char(true); public final static ActionListener SELECT_PREV_LINE = new prev_line(true); public final static ActionListener SELECT_PREV_PAGE = new prev_page(true); public final static ActionListener SELECT_PREV_WORD = new prev_word(true); public final static ActionListener[] ACTIONS = { BACKSPACE, DELETE, END, SELECT_END, INSERT_BREAK, INSERT_TAB, HOME, SELECT_HOME, NEXT_CHAR, NEXT_LINE, NEXT_PAGE, NEXT_WORD, SELECT_NEXT_CHAR, SELECT_NEXT_LINE, SELECT_NEXT_PAGE, SELECT_NEXT_WORD, OVERWRITE, PREV_CHAR, PREV_LINE, PREV_PAGE, PREV_WORD, SELECT_PREV_CHAR, SELECT_PREV_LINE, SELECT_PREV_PAGE, SELECT_PREV_WORD}; public final static String[] ACTION_NAMES = { "backspace", "delete", "end", "select-end", "insert-break", "insert-tab", "home", "select-home", "next-char", "next-line", "next-page", "next-word", "select-next-char", "select-next-line", "select-next-page", "select-next-word", "overwrite", "prev-char", "prev-line", "prev-page", "prev-word", "select-prev-char", "select-prev-line", "select-prev-page", "select-prev-word"}; // private members private Hashtable bindings; private Hashtable currentBindings; } class ScrollLayout implements LayoutManager { public void addLayoutComponent(String name, Component comp) { if (name.equals(CENTER)) { center = comp; } else if (name.equals(RIGHT)) { right = comp; } else if (name.equals(BOTTOM)) { bottom = comp; } } public void removeLayoutComponent(Component comp) { if (center == comp) { center = null; } if (right == comp) { right = null; } if (bottom == comp) { bottom = null; } } public Dimension preferredLayoutSize(Container parent) { Dimension dim = new Dimension(); Insets insets = getInsets(); dim.width = insets.left + insets.right; dim.height = insets.top + insets.bottom; Dimension centerPref = center.getPreferredSize(); dim.height += centerPref.height; dim.width += centerPref.width; Dimension rightPref = right.getPreferredSize(); dim.width += rightPref.width; /* Dimension bottomPref = bottom.getPreferredSize(); dim.height += bottomPref.height; */ return dim; } public Dimension minimumLayoutSize(Container parent) { Dimension dim = new Dimension(); Insets insets = getInsets(); dim.width = insets.left + insets.right; dim.height = insets.top + insets.bottom; Dimension centerPref = center.getMinimumSize(); dim.height += centerPref.height; dim.width += centerPref.width; Dimension rightPref = right.getMinimumSize(); dim.width += rightPref.width; Dimension bottomPref = bottom.getMinimumSize(); dim.height += bottomPref.height; return dim; } public void layoutContainer(Container parent) { Dimension size = parent.getSize(); Insets insets = getInsets(); int itop = insets.top; int ileft = insets.left; int ibottom = insets.bottom; int iright = insets.right; int rightWidth = right.getPreferredSize().width; int bottomHeight = center.getPreferredSize().height; center.setBounds( ileft, itop, size.width - rightWidth - ileft - iright, size.height - bottomHeight - itop - ibottom); right.setBounds( size.width - rightWidth - iright, itop, rightWidth, size.height - bottomHeight - itop - ibottom); /* bottom.setBounds( ileft, size.height - bottomHeight - ibottom, size.width - rightWidth - ileft - iright, bottomHeight); */ } // private members private Component center; private Component right; private Component bottom; } class AutoScroll implements ActionListener, MouseMotionListener { public void actionPerformed(ActionEvent evt) { select(getMarkPosition(), xyToOffset(x, y)); } public void mouseDragged(MouseEvent evt) { if (popup != null && popup.isVisible()) { return; } x = evt.getX(); y = evt.getY(); if (!scrollTimer.isRunning()) { scrollTimer.start(); } } public void mouseMoved(MouseEvent evt) { } private int x, y; } class MutableCaretEvent extends CaretEvent { MutableCaretEvent() { super(ACLTextArea.this); } public int getDot() { return getCaretPosition(); } public int getMark() { return getMarkPosition(); } } class AdjustHandler implements AdjustmentListener { public void adjustmentValueChanged(AdjustmentEvent evt) { if (!scrollBarsInitialized) { return; } if (evt.getAdjustable() == vertical) { setFirstLine(vertical.getValue()); } else { setHorizontalOffset(-horizontal.getValue()); } } } class ComponentHandler extends ComponentAdapter { public void componentResized(ComponentEvent evt) { recalculateVisibleLines(); scrollBarsInitialized = true; } } class DocumentHandler implements DocumentListener { public void insertUpdate(DocumentEvent evt) { documentChanged(evt); int offset = evt.getOffset(); int length = evt.getLength(); boolean repaint = true; int newStart; int newEnd; if (selectionStart >= offset) { newStart = selectionStart + length; repaint = false; } else { newStart = selectionStart; } if (selectionEnd >= offset) { newEnd = selectionEnd + length; repaint = false; } else { newEnd = selectionEnd; } select(newStart, newEnd); if (repaint) { painter.fastRepaint(); } } public void removeUpdate(DocumentEvent evt) { documentChanged(evt); int offset = evt.getOffset(); int length = evt.getLength(); boolean repaint = true; int newStart; int newEnd; if (selectionStart > offset) { if (selectionStart > offset + length) { newStart = selectionStart - length; } else { newStart = offset; } repaint = false; } else { newStart = selectionStart; } if (selectionEnd > offset) { if (selectionEnd > offset + length) { newEnd = selectionEnd - length; } else { newEnd = offset; } repaint = false; } else { newEnd = selectionEnd; } select(newStart, newEnd); if (repaint) { painter.fastRepaint(); } } public void changedUpdate(DocumentEvent evt) { } } class FocusHandler implements FocusListener { public void focusGained(FocusEvent evt) { setCaretVisible(true); focusedComponent = ACLTextArea.this; } public void focusLost(FocusEvent evt) { setCaretVisible(false); focusedComponent = null; } } class MouseHandler extends MouseAdapter { public void mousePressed(MouseEvent evt) { requestFocus(); if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) != 0 && popup != null) { popup.show(painter, evt.getX(), evt.getY()); return; } int line = yToLine(evt.getY()); int offset = xToOffset(line, evt.getX()); int dot = getLineStartOffset(line) + offset; switch (evt.getClickCount()) { case 1: doSingleClick(evt, line, offset, dot); break; case 2: // It uses the bracket matching stuff, so // it can throw a BLE try { doDoubleClick(evt, line, offset, dot); } catch (BadLocationException bl) { bl.printStackTrace(); } break; case 3: doTripleClick(evt, line, offset, dot); break; } } public void mouseReleased(MouseEvent evt) { if (scrollTimer.isRunning()) { scrollTimer.stop(); } } private void doSingleClick(MouseEvent evt, int line, int offset, int dot) { if ((evt.getModifiers() & InputEvent.SHIFT_MASK) != 0) { setSelectionEnd(dot); } else { setCaretPosition(dot); } } private void doDoubleClick(MouseEvent evt, int line, int offset, int dot) throws BadLocationException { // Ignore empty lines if (getLineLength(line) == 0) { return; } try { int bracket = TextUtilities.findMatchingBracket( document, Math.max(0, dot - 1)); if (bracket != -1) { int mark = getMarkPosition(); // Hack if (bracket > mark) { bracket++; mark--; } select(mark, bracket); return; } } catch (BadLocationException bl) { bl.printStackTrace(); } // Ok, it's not a bracket... select the word String lineText = getLineText(line); char ch = lineText.charAt(offset - 1); String noWordSep = (String)document.getProperty("noWordSep"); if (noWordSep == null) { noWordSep = ""; } // If the user clicked on a non-letter char, // we select the surrounding non-letters boolean selectNoLetter = (!Character.isLetterOrDigit(ch) && noWordSep.indexOf(ch) == -1); int wordStart = 0; for (int i = offset - 1; i >= 0; i--) { ch = lineText.charAt(i); if (selectNoLetter ^ (!Character.isLetterOrDigit(ch) && noWordSep.indexOf(ch) == -1)) { wordStart = i + 1; break; } } int wordEnd = lineText.length(); for (int i = offset; i < lineText.length(); i++) { ch = lineText.charAt(i); if (selectNoLetter ^ (!Character.isLetterOrDigit(ch) && noWordSep.indexOf(ch) == -1)) { wordEnd = i; break; } } int lineStart = getLineStartOffset(line); select(lineStart + wordStart, lineStart + wordEnd); } private void doTripleClick(MouseEvent evt, int line, int offset, int dot) { select(getLineStartOffset(line), getLineEndOffset(line) - 1); } } // protected members protected static String CENTER = "center"; protected static String RIGHT = "right"; protected static String BOTTOM = "bottom"; protected static ACLTextArea focusedComponent; protected static Timer caretTimer; protected ACLTextAreaPainter painter; protected JPopupMenu popup; protected Timer scrollTimer; protected EventListenerList listenerList; protected MutableCaretEvent caretEvent; protected boolean caretBlinks; protected boolean caretVisible; protected boolean blink; protected boolean editable; protected int firstLine; protected int visibleLines; protected int electricScroll; protected int horizontalOffset; protected JScrollBar vertical; protected JScrollBar horizontal; protected boolean scrollBarsInitialized; protected InputHandler inputHandler; protected ACLSyntaxDocument document; protected DocumentHandler documentHandler; protected Segment lineSegment; protected int selectionStart; protected int selectionStartLine; protected int selectionEnd; protected int selectionEndLine; protected boolean biasLeft; protected int bracketPosition; protected int bracketLine; protected int magicCaret; protected boolean overwrite; private String contentLanguage = ""; private ACLMessage msg; private String fieldName; } // ***EOF***