All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fife.ui.rtextarea.RTextAreaEditorKit Maven / Gradle / Ivy

Go to download

RSyntaxTextArea is the syntax highlighting text editor for Swing applications. Features include syntax highlighting for 40+ languages, code folding, code completion, regex find and replace, macros, code templates, undo/redo, line numbering and bracket matching.

There is a newer version: 3.5.1
Show newest version
/*
 * 08/13/2004
 *
 * RTextAreaEditorKit.java - The editor kit used by RTextArea.
 * Copyright (C) 2004 Robert Futrell
 * robert_futrell at users.sourceforge.net
 * http://fifesoft.com/rsyntaxtextarea
 *
 * 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; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * 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 org.fife.ui.rtextarea;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.text.BreakIterator;
import java.text.DateFormat;
import java.util.Date;
import javax.swing.*;
import javax.swing.text.*;


/**
 * An extension of DefaultEditorKit that adds functionality found
 * in RTextArea.
 *
 * @author Robert Futrell
 * @version 0.1
 */
// FIXME:  Replace Utilities calls with custom versions (in RSyntaxUtilities) to
// cut down on all of the modelToViews, as each call causes
// a getTokenList => expensive!
public class RTextAreaEditorKit extends DefaultEditorKit {

	/**
	 * The name of the action that begins recording a macro.
	 */
	public static final String rtaBeginRecordingMacroAction	= "RTA.BeginRecordingMacroAction";

	/**
	 * The name of the action to decrease the font size.
	 */
	public static final String rtaDecreaseFontSizeAction		= "RTA.DecreaseFontSizeAction";

	/**
	 * The name of the action that deletes the current line.
	 */
	public static final String rtaDeleteLineAction			= "RTA.DeleteLineAction";

	/**
	 * The name of the action to delete the word before the caret.
	 */
	public static final String rtaDeletePrevWordAction		= "RTA.DeletePrevWordAction";

	/**
	 * The name of the action taken to delete the remainder of the line (from
	 * the caret position to the end of the line).
	 */
	public static final String rtaDeleteRestOfLineAction		= "RTA.DeleteRestOfLineAction";

	/**
	 * The name of the action that completes the word at the caret position
	 * with the last word in the document that starts with the text up to the
	 * caret.
	 */
	public static final String rtaDumbCompleteWordAction		= "RTA.DumbCompleteWordAction";

	/**
	 * The name of the action that ends recording a macro.
	 */
	public static final String rtaEndRecordingMacroAction		= "RTA.EndRecordingMacroAction";

	/**
	 * The name of the action to increase the font size.
	 */
	public static final String rtaIncreaseFontSizeAction		= "RTA.IncreaseFontSizeAction";

	/**
	 * The name of the action that inverts the case of the current selection.
	 */
	public static final String rtaInvertSelectionCaseAction	= "RTA.InvertCaseAction";

	/**
	 * The name of the action to join two lines.
	 */
	public static final String rtaJoinLinesAction			= "RTA.JoinLinesAction";

	/**
	 * Action to move a line down.
	 */
	public static final String rtaLineDownAction				= "RTA.LineDownAction";

	/**
	 * Action to move a line up.
	 */
	public static final String rtaLineUpAction				= "RTA.LineUpAction";

	/**
	 * The name of the action to make the current selection lower-case.
	 */
	public static final String rtaLowerSelectionCaseAction		= "RTA.LowerCaseAction";

	/**
	 * Action to jump to the next bookmark.
	 */
	public static final String rtaNextBookmarkAction		= "RTA.NextBookmarkAction";

	/**
	 * Action to jump to the previous bookmark.
	 */
	public static final String rtaPrevBookmarkAction		= "RTA.PrevBookmarkAction";

	/**
	 * The name of the action that "plays back" the last macro.
	 */
	public static final String rtaPlaybackLastMacroAction		= "RTA.PlaybackLastMacroAction";

	/**
	 * The name of the action for "redoing" the last action undone.
	 */
	public static final String rtaRedoAction				= "RTA.RedoAction";

	/**
	 * The name of the action to scroll the text area down one line
	 * without changing the caret's position.
	 */
	public static final String rtaScrollDownAction			= "RTA.ScrollDownAction";

	/**
	 * The name of the action to scroll the text area up one line
	 * without changing the caret's position.
	 */
	public static final String rtaScrollUpAction				= "RTA.ScrollUpAction";

	/**
	 * The name of the action for "paging up" with the selection.
	 */
	public static final String rtaSelectionPageUpAction		= "RTA.SelectionPageUpAction";

	/**
	 * The name of the action for "paging down" with the selection.
	 */
	public static final String rtaSelectionPageDownAction		= "RTA.SelectionPageDownAction";

	/**
	 * The name of the action for "paging left" with the selection.
	 */
	public static final String rtaSelectionPageLeftAction		= "RTA.SelectionPageLeftAction";

	/**
	 * The name of the action for "paging right" with the selection.
	 */
	public static final String rtaSelectionPageRightAction		= "RTA.SelectionPageRightAction";

	/**
	 * The name of the action for inserting a time/date stamp.
	 */
	public static final String rtaTimeDateAction				= "RTA.TimeDateAction";

	/**
	 * Toggles whether the current line has a bookmark, if this text area
	 * is in an {@link RTextScrollPane}.
	 */
	public static final String rtaToggleBookmarkAction		= "RTA.ToggleBookmarkAction";

	/**
	 * The name of the action taken when the user hits the Insert key (thus
	 * toggling between insert and overwrite modes).
	 */
	public static final String rtaToggleTextModeAction		= "RTA.ToggleTextModeAction";

	/**
	 * The name of the action for "undoing" the last action done.
	 */
	public static final String rtaUndoAction				= "RTA.UndoAction";

	/**
	 * The name of the action for unselecting any selected text in the text
	 * area.
	 */
	public static final String rtaUnselectAction				= "RTA.UnselectAction";

	/**
	 * The name of the action for making the current selection upper-case.
	 */
	public static final String rtaUpperSelectionCaseAction		= "RTA.UpperCaseAction";

	/**
	 * The actions that RTextAreaEditorKit adds to those of
	 * the default editor kit.
	 */
	private static final RecordableTextAction[] defaultActions = {
		new BeginAction(beginAction, false), 
		new BeginAction(selectionBeginAction, true), 
		new BeginLineAction(beginLineAction, false),  
		new BeginLineAction(selectionBeginLineAction, true),  
		new BeginRecordingMacroAction(),
		new BeginWordAction(beginWordAction, false),
		new BeginWordAction(selectionBeginWordAction, true),
		new CopyAction(),
		new CutAction(),
		new DefaultKeyTypedAction(),
		new DeleteLineAction(),
		new DeleteNextCharAction(),
		new DeletePrevCharAction(),
		new DeletePrevWordAction(),
		new DeleteRestOfLineAction(),
		new DumbCompleteWordAction(),
		new EndAction(endAction, false),
		new EndAction(selectionEndAction, true),
		new EndLineAction(endLineAction, false),
		new EndLineAction(selectionEndLineAction, true),
		new EndRecordingMacroAction(),
		new EndWordAction(endWordAction, false),
		new EndWordAction(endWordAction, true),
		new InsertBreakAction(),
		new InsertContentAction(),
		new InsertTabAction(),
		new InvertSelectionCaseAction(),
		new JoinLinesAction(),
		new LowerSelectionCaseAction(),
		new LineMoveAction(rtaLineUpAction, -1),
		new LineMoveAction(rtaLineDownAction, 1),
		new NextBookmarkAction(rtaNextBookmarkAction, true),
		new NextBookmarkAction(rtaPrevBookmarkAction, false),
		new NextVisualPositionAction(forwardAction, false, SwingConstants.EAST),
		new NextVisualPositionAction(backwardAction, false, SwingConstants.WEST),
		new NextVisualPositionAction(selectionForwardAction, true, SwingConstants.EAST),
		new NextVisualPositionAction(selectionBackwardAction, true, SwingConstants.WEST),
		new NextVisualPositionAction(upAction, false, SwingConstants.NORTH),
		new NextVisualPositionAction(downAction, false, SwingConstants.SOUTH),
		new NextVisualPositionAction(selectionUpAction, true, SwingConstants.NORTH),
		new NextVisualPositionAction(selectionDownAction, true, SwingConstants.SOUTH),
		new NextWordAction(nextWordAction, false),
		new NextWordAction(selectionNextWordAction, true),
		new PageAction(rtaSelectionPageLeftAction, true, true), 
		new PageAction(rtaSelectionPageRightAction, false, true),
		new PasteAction(),
		new PlaybackLastMacroAction(),
		new PreviousWordAction(previousWordAction, false),  
		new PreviousWordAction(selectionPreviousWordAction, true),
		new RedoAction(),
		new ScrollAction(rtaScrollUpAction, -1),
		new ScrollAction(rtaScrollDownAction, 1),
		new SelectAllAction(),
		new SelectLineAction(),
		new SelectWordAction(),
		new SetReadOnlyAction(),
		new SetWritableAction(),
		new ToggleBookmarkAction(),
		new ToggleTextModeAction(),
		new UndoAction(),
		new UnselectAction(),
		new UpperSelectionCaseAction(),
		new VerticalPageAction(pageUpAction, -1, false), 
		new VerticalPageAction(pageDownAction, 1, false),
		new VerticalPageAction(rtaSelectionPageUpAction, -1, true), 
		new VerticalPageAction(rtaSelectionPageDownAction, 1, true)
	};

	/**
	 * The amount of characters read at a time when reading a file.
	 */
	private static final int READBUFFER_SIZE	= 32768;


	/**
	 * Constructor.
	 */
	public RTextAreaEditorKit() {
		super();
	}


	/**
	 * Fetches the set of commands that can be used
	 * on a text component that is using a model and
	 * view produced by this kit.
	 *
	 * @return the command list
	 */ 
	public Action[] getActions() {
		return defaultActions;
	}


	/**
	 * Inserts content from the given stream, which will be 
	 * treated as plain text.  This method is overridden merely
	 * so we can increase the number of characters read at a time.
	 * 
	 * @param in  The stream to read from
	 * @param doc The destination for the insertion.
	 * @param pos The location in the document to place the
	 *   content >= 0.
	 * @exception IOException on any I/O error
	 * @exception BadLocationException if pos represents an invalid
	 *   location within the document.
	*/
	public void read(Reader in, Document doc, int pos) 
				throws IOException, BadLocationException {

		char[] buff = new char[READBUFFER_SIZE];
		int nch;
		boolean lastWasCR = false;
		boolean isCRLF = false;
		boolean isCR = false;
		int last;
		boolean wasEmpty = (doc.getLength() == 0);

		// Read in a block at a time, mapping \r\n to \n, as well as single
		// \r's to \n's. If a \r\n is encountered, \r\n will be set as the
		// newline string for the document, if \r is encountered it will
		// be set as the newline character, otherwise the newline property
		// for the document will be removed.
		while ((nch = in.read(buff, 0, buff.length)) != -1) {
			last = 0;
			for (int counter = 0; counter < nch; counter++) {
				switch (buff[counter]) {
					case '\r':
						if (lastWasCR) {
							isCR = true;
							if (counter == 0) {
								doc.insertString(pos, "\n", null);
								pos++;
							}
							else {
								buff[counter - 1] = '\n';
							}
						}
						else {
							lastWasCR = true;
						}
						break;
					case '\n':
						if (lastWasCR) {
							if (counter > (last + 1)) {
								doc.insertString(pos, new String(buff, last,
												counter - last - 1), null);
								pos += (counter - last - 1);
							}
							// else nothing to do, can skip \r, next write will
							// write \n
							lastWasCR = false;
							last = counter;
							isCRLF = true;
						}
						break;
					default:
						if (lastWasCR) {
							isCR = true;
							if (counter == 0) {
								doc.insertString(pos, "\n", null);
								pos++;
							}
							else {
								buff[counter - 1] = '\n';
							}
							lastWasCR = false;
						}
						break;
				} // End of switch (buff[counter]).
			} // End of for (int counter = 0; counter < nch; counter++).

			if (last < nch) {
				if(lastWasCR) {
					if (last < (nch - 1)) {
						doc.insertString(pos, new String(buff, last,
										nch - last - 1), null);
						pos += (nch - last - 1);
					}
				}
				else {
					doc.insertString(pos, new String(buff, last,
									nch - last), null);
					pos += (nch - last);
				}
			}

		} // End of while ((nch = in.read(buff, 0, buff.length)) != -1).

		if (lastWasCR) {
			doc.insertString(pos, "\n", null);
			isCR = true;
		}

		if (wasEmpty) {
			if (isCRLF) {
				doc.putProperty(EndOfLineStringProperty, "\r\n");
			}
			else if (isCR) {
				doc.putProperty(EndOfLineStringProperty, "\r");
			}
			else {
				doc.putProperty(EndOfLineStringProperty, "\n");
			}
		}

	}


	/**
	 * Creates a beep.
	 */
	public static class BeepAction extends RecordableTextAction {

		public BeepAction() {
			super(beepAction);
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			UIManager.getLookAndFeel().provideErrorFeedback(textArea);
		}

		public final String getMacroID() {
			return beepAction;
		}

	}


	/**
	 * Moves the caret to the beginning of the document.
	 */
	public static class BeginAction extends RecordableTextAction {

 		private boolean select;

		public BeginAction(String name, boolean select) {
			super(name);
			this.select = select;
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			if (select)
				textArea.moveCaretPosition(0);
			else
				textArea.setCaretPosition(0);
		}

		public final String getMacroID() {
			return getName();
		}

	}


	/**
	 * Toggles the position of the caret between the beginning of the line,
	 * and the first non-whitespace character on the line.
	 */
	public static class BeginLineAction extends RecordableTextAction {

 		private Segment currentLine = new Segment(); // For speed.
 		private boolean select;

		public BeginLineAction(String name, boolean select) {
			super(name);
			this.select = select;
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {

			int newPos = 0;

			try {

				// Is line wrap enabled?
				if (textArea.getLineWrap()) {
					int offs = textArea.getCaretPosition();
					// TODO:  Replace Utilities call with custom version
					// to cut down on all of the modelToViews, as each call
					// causes TokenList => expensive!
					int begOffs = Utilities.getRowStart(textArea, offs);
					// TODO: line wrap doesn't currently toggle between
					// the first non-whitespace char and the actual start
					// of the line line the no-line-wrap version does.
					newPos = begOffs;
				}

				// No line wrap - optimized for performance!
				else {

					// We use the elements instead of calling
					// getLineOfOffset(), etc. to speed things up just a
					// tad (i.e. micro-optimize).
					int caretPosition = textArea.getCaretPosition();
					Document document = textArea.getDocument();
					Element map = document.getDefaultRootElement();
					int currentLineNum = map.getElementIndex(caretPosition);
					Element currentLineElement = map.getElement(currentLineNum);
					int currentLineStart = currentLineElement.getStartOffset();
					int currentLineEnd = currentLineElement.getEndOffset();
					int count = currentLineEnd - currentLineStart;
					if (count>0) { // If there are chars in the line...
						document.getText(currentLineStart, count, currentLine);
						int firstNonWhitespace = getFirstNonWhitespacePos();
						firstNonWhitespace = currentLineStart +
								(firstNonWhitespace - currentLine.offset);
						if (caretPosition!=firstNonWhitespace) {
							newPos = firstNonWhitespace;
						}
						else {
							newPos = currentLineStart;
						}
					}
					else { // Empty line (at end of the document only).
						newPos = currentLineStart;
					}

				}

				if (select) {
					textArea.moveCaretPosition(newPos);
				}
				else {
					textArea.setCaretPosition(newPos);
				}
				//e.consume();

			} catch (BadLocationException ble) {
				/* Shouldn't ever happen. */
				UIManager.getLookAndFeel().provideErrorFeedback(textArea);
				ble.printStackTrace();
			}

		}

		private final int getFirstNonWhitespacePos() {
			int offset = currentLine.offset;
			int end = offset + currentLine.count - 1;
			int pos = offset;
			char[] array = currentLine.array;
			char currentChar = array[pos];
			while ((currentChar=='\t' || currentChar==' ') && (++pos=MINIMUM_SIZE) {
				// Shrink by decreaseAmount.
				font = font.deriveFont(newSize);
				textArea.setFont(font);
			}
			else if (oldSize>MINIMUM_SIZE) {
				// Can't shrink by full decreaseAmount, but can shrink a
				// little bit.
				font = font.deriveFont(MINIMUM_SIZE);
				textArea.setFont(font);
			}
			else {
				// Our font size must be at or below MINIMUM_SIZE.
				UIManager.getLookAndFeel().provideErrorFeedback(textArea);
			}
			textArea.requestFocusInWindow();
		}

		public final String getMacroID() {
			return rtaDecreaseFontSizeAction;
		}

		protected void initialize() {
			decreaseAmount = 1.0f;
		}

	}


	/**
	 * The action to use when no actions in the input/action map meet the key
	 * pressed.  This is actually called from the keymap I believe.
	 */
	public static class DefaultKeyTypedAction extends RecordableTextAction {

		private Action delegate;

		public DefaultKeyTypedAction() {
			super(DefaultEditorKit.defaultKeyTypedAction, null, null, null,
					null);
			delegate = new DefaultEditorKit.DefaultKeyTypedAction();
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			// DefaultKeyTypedAction *is* different across different JVM's
			// (at least the OSX implementation must be different - Alt+Numbers
			// inputs symbols such as '[', '{', etc., which is a *required*
			// feature on MacBooks running with non-English input, such as
			// German or Swedish Pro).  So we can't just copy the
			// implementation, we must delegate to it.
			delegate.actionPerformed(e);
		}

		public final String getMacroID() {
			return DefaultEditorKit.defaultKeyTypedAction;
		}

	}


	/**
	 * Deletes the current line(s).
	 */
	public static class DeleteLineAction extends RecordableTextAction {

		public DeleteLineAction() {
			super(RTextAreaEditorKit.rtaDeleteLineAction, null, null, null,
						null);
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {

			if (!textArea.isEditable() || !textArea.isEnabled()) {
				UIManager.getLookAndFeel().provideErrorFeedback(textArea);
				return;
			}

			int selStart = textArea.getSelectionStart();
			int selEnd   = textArea.getSelectionEnd();

			try {

				int line1     = textArea.getLineOfOffset(selStart);
				int startOffs = textArea.getLineStartOffset(line1);
				int line2     = textArea.getLineOfOffset(selEnd);
				int endOffs   = textArea.getLineEndOffset(line2);

				// Don't remove the last line if no actual chars are selected
				if (line2>line1) {
					if (selEnd==textArea.getLineStartOffset(line2)) {
						endOffs = selEnd;
					}
				}

				textArea.replaceRange(null, startOffs, endOffs);

			} catch (BadLocationException ble) {
				ble.printStackTrace(); // Never happens
			}

		}

		public final String getMacroID() {
			return RTextAreaEditorKit.rtaDeleteLineAction;
		}

	}


	/**
	 * Deletes the character of content that follows the current caret
	 * position.
	 */
	public static class DeleteNextCharAction extends RecordableTextAction {

		public DeleteNextCharAction() {
			super(DefaultEditorKit.deleteNextCharAction, null, null,
												null, null);
		}

		public DeleteNextCharAction(String name, Icon icon, String desc,
					Integer mnemonic, KeyStroke accelerator) {
			super(name, icon, desc, mnemonic, accelerator);
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {

			boolean beep = true;
			if ((textArea != null) && (textArea.isEditable())) {
				try {
					Document doc = textArea.getDocument();
					Caret caret = textArea.getCaret();
					int dot = caret.getDot();
					int mark = caret.getMark();
					if (dot != mark) {
						doc.remove(Math.min(dot, mark), Math.abs(dot - mark));
						beep = false;
					}
					else if (dot < doc.getLength()) {
						int delChars = 1;
						if (dot < doc.getLength() - 1) {
							String dotChars = doc.getText(dot, 2);
							char c0 = dotChars.charAt(0);
							char c1 = dotChars.charAt(1);
							if (c0 >= '\uD800' && c0 <= '\uDBFF' &&
								c1 >= '\uDC00' && c1 <= '\uDFFF') {
								delChars = 2;
							}
						}
						doc.remove(dot, delChars);
						beep = false;
					}
				} catch (BadLocationException bl) {
				}
			}

			if (beep)
				UIManager.getLookAndFeel().provideErrorFeedback(textArea);

			textArea.requestFocusInWindow();

		}

		public final String getMacroID() {
			return DefaultEditorKit.deleteNextCharAction;
		}

	}


	/**
	 * Deletes the character of content that precedes the current caret
	 * position.
	 */
	public static class DeletePrevCharAction extends RecordableTextAction {
 
		public DeletePrevCharAction() {
			super(deletePrevCharAction);
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {

			boolean beep = true;
			if ((textArea != null) && (textArea.isEditable())) {
				try {
					Document doc = textArea.getDocument();
					Caret caret = textArea.getCaret();
					int dot = caret.getDot();
					int mark = caret.getMark();
					if (dot != mark) {
						doc.remove(Math.min(dot, mark), Math.abs(dot - mark));
						beep = false;
					}
					else if (dot > 0) {
						int delChars = 1;
						if (dot > 1) {
							String dotChars = doc.getText(dot - 2, 2);
							char c0 = dotChars.charAt(0);
							char c1 = dotChars.charAt(1);
							if (c0 >= '\uD800' && c0 <= '\uDBFF' &&
								c1 >= '\uDC00' && c1 <= '\uDFFF') {
								delChars = 2;
							}
						}
						doc.remove(dot - delChars, delChars);
						beep = false;
					}
				} catch (BadLocationException bl) {
				}
			}

			if (beep)
				UIManager.getLookAndFeel().provideErrorFeedback(textArea);

		}

		public final String getMacroID() {
			return DefaultEditorKit.deletePrevCharAction;
		}

	}


	/**
	 * Action that deletes the previous word in the text area.
	 */
	public static class DeletePrevWordAction extends RecordableTextAction {

		public DeletePrevWordAction() {
			super(rtaDeletePrevWordAction);
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			if (!textArea.isEditable() || !textArea.isEnabled()) {
				UIManager.getLookAndFeel().provideErrorFeedback(textArea);
				return;
			}
			try {
				int end = textArea.getSelectionStart();
				int start = Utilities.getPreviousWord(textArea, end);
				if (end>start) {
					textArea.getDocument().remove(start, end-start);
				}
			} catch (Exception ex) {
				UIManager.getLookAndFeel().provideErrorFeedback(textArea);
			}
		}

		public String getMacroID() {
			return rtaDeletePrevWordAction;
		}

	}


	/**
	 * Action that deletes all text from the caret position to the end of the
	 * caret's line.
	 */
	public static class DeleteRestOfLineAction extends RecordableTextAction {

 
		public DeleteRestOfLineAction() {
			super(rtaDeleteRestOfLineAction);
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {

			try {

				// We use the elements instead of calling getLineOfOffset(),
				// etc. to speed things up just a tad (i.e. micro-optimize).
				Document document = textArea.getDocument();
				int caretPosition = textArea.getCaretPosition();
				Element map = document.getDefaultRootElement();
				int currentLineNum = map.getElementIndex(caretPosition);
				Element currentLineElement = map.getElement(currentLineNum);
				// Always take -1 as we don't want to remove the newline.
				int currentLineEnd = currentLineElement.getEndOffset()-1;
				if (caretPosition 0) {
					int wordStart = Utilities.getPreviousWord(textArea,
							searchOffs);
					if (wordStart==0 || wordStart==BreakIterator.DONE) {
						UIManager.getLookAndFeel().provideErrorFeedback(
								textArea);
						break;
					}
					int end = Utilities.getWordEnd(textArea, wordStart);
					String word = textArea.getText(wordStart, end - wordStart);
					searchOffs = wordStart;
					if (word.startsWith(lastPrefix)) {
						textArea.replaceRange(word, lastWordStart, dot);
						lastDot = textArea.getCaretPosition(); // Maybe shifted
						break;
					}
				}

			} catch (BadLocationException ble) { // Never happens
				ble.printStackTrace();
			}

		}

		public final String getMacroID() {
			return getName();
		}

	}


	/**
	 * Moves the caret to the end of the document.
	 */
	public static class EndAction extends RecordableTextAction {

 		private boolean select;

		public EndAction(String name, boolean select) {
			super(name);
			this.select = select;
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			Document doc = textArea.getDocument();
			int dot = doc.getLength();
			if (select)
				textArea.moveCaretPosition(dot);
			else
				textArea.setCaretPosition(dot);
		}

		public final String getMacroID() {
			return getName();
		}

	}


	/**
	 * Positions the caret at the end of the line.
	 */
	public static class EndLineAction extends RecordableTextAction {

 		private boolean select;

		public EndLineAction(String name, boolean select) {
			super(name);
			this.select = select;
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			try {
				int offs = textArea.getCaretPosition();
				// FIXME:  Replace Utilities call with custom version to
				// cut down on all of the modelToViews, as each call causes
				// a getTokenList => expensive!
				int endOffs = Utilities.getRowEnd(textArea, offs);
				if (select) {
					textArea.moveCaretPosition(endOffs);
				}
				else {
					textArea.setCaretPosition(endOffs);
				}
			} catch (Exception ex) {
				UIManager.getLookAndFeel().provideErrorFeedback(textArea);
			}
		}

		public final String getMacroID() {
			return getName();
		}

	}


	/**
	 * Action that ends recording a macro.
	 */
	public static class EndRecordingMacroAction extends RecordableTextAction {

 
		public EndRecordingMacroAction() {
			super(rtaEndRecordingMacroAction);
		}

		public EndRecordingMacroAction(String name, Icon icon,
					String desc, Integer mnemonic, KeyStroke accelerator) {
			super(name, icon, desc, mnemonic, accelerator);
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			RTextArea.endRecordingMacro();
		}

		public final String getMacroID() {
			return rtaEndRecordingMacroAction;
		}

		public boolean isRecordable() {
			return false; // Never record the recording of a macro!
		}

	}


	/**
	 * Positions the caret at the end of the word.
	 */
	protected static class EndWordAction extends RecordableTextAction {

 		private boolean select;

		protected EndWordAction(String name, boolean select) {
			super(name);
			this.select = select;
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			try {
				int offs = textArea.getCaretPosition();
				int endOffs = getWordEnd(textArea, offs);
				if (select)
					textArea.moveCaretPosition(endOffs);
				else
					textArea.setCaretPosition(endOffs);
			} catch (BadLocationException ble) {
				UIManager.getLookAndFeel().provideErrorFeedback(textArea);
			}
		}

		public final String getMacroID() {
			return getName();
		}

		protected int getWordEnd(RTextArea textArea, int offs)
									throws BadLocationException {
			return Utilities.getWordEnd(textArea, offs);
		}

	}


	/**
	 * Action for increasing the font size.
	 */
	public static class IncreaseFontSizeAction extends RecordableTextAction {

 
		protected float increaseAmount;

		protected static final float MAXIMUM_SIZE	= 40.0f;

		public IncreaseFontSizeAction() {
			super(rtaIncreaseFontSizeAction);
			initialize();
		}

		public IncreaseFontSizeAction(String name, Icon icon, String desc,
							Integer mnemonic, KeyStroke accelerator) {
			super(name, icon, desc, mnemonic, accelerator);
			initialize();
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			Font font = textArea.getFont();
			float oldSize = font.getSize2D();
			float newSize = oldSize + increaseAmount;
			if (newSize<=MAXIMUM_SIZE) {
				// Grow by increaseAmount.
				font = font.deriveFont(newSize);
				textArea.setFont(font);
			}
			else if (oldSize0) {
					moveLineUp(textArea, line);
				}
				else if (moveAmt==1 && linecurLine) {
										moveTo = bookmark;
										break;
									}
								}
								if (moveTo==null) { // Loop back to beginning
									moveTo = bookmarks[0];
								}
							}
							else {
								for (int i=bookmarks.length-1; i>=0; i--) {
									GutterIconInfo bookmark = bookmarks[i];
									int offs = bookmark.getMarkedOffset();
									int line = textArea.getLineOfOffset(offs);
									if (line= curPara.getEndOffset() &&
							oldOffs != curPara.getEndOffset() - 1) {
					// we should first move to the end of current paragraph
					// http://bugs.sun.com/view_bug.do?bug_id=4278839
					offs = curPara.getEndOffset() - 1;
				}
			} catch (BadLocationException ble) {
				int end = textArea.getDocument().getLength();
				if (offs != end) {
					if(oldOffs != curPara.getEndOffset() - 1)
						offs = curPara.getEndOffset() - 1;
					else
						offs = end;
				}
			}

			if (select)
				textArea.moveCaretPosition(offs);
			else
				textArea.setCaretPosition(offs);

		}

		public final String getMacroID() {
			return getName();
		}

		protected int getNextWord(RTextArea textArea, int offs)
									throws BadLocationException {
			return Utilities.getNextWord(textArea, offs);
		}

	}


	/**
	 * Pages one view to the left or right.
	 */
	static class PageAction extends RecordableTextAction {

 
		private boolean select;
		private boolean left;

		public PageAction(String name, boolean left, boolean select) {
			super(name);
			this.select = select;
			this.left = left;
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {

			int selectedIndex;
			Rectangle visible = new Rectangle();
			textArea.computeVisibleRect(visible);
			if (left)
				visible.x = Math.max(0, visible.x - visible.width);
			else
				visible.x += visible.width;
		
			selectedIndex = textArea.getCaretPosition();
			if(selectedIndex != -1) {
				if (left) {
					selectedIndex = textArea.viewToModel(
									new Point(visible.x, visible.y));
				}
				else {
					selectedIndex = textArea.viewToModel(
							new Point(visible.x + visible.width - 1,
									visible.y + visible.height - 1));
				}
				Document doc = textArea.getDocument();
				if ((selectedIndex != 0) && 
					(selectedIndex  > (doc.getLength()-1))) {
					selectedIndex = doc.getLength()-1;
				}
				else if(selectedIndex  < 0) {
					selectedIndex = 0;
				}
				if (select)
					textArea.moveCaretPosition(selectedIndex);
				else
					textArea.setCaretPosition(selectedIndex);
			}

		}

		public final String getMacroID() {
			return getName();
		}

	}


	/**
	 * Action for pasting text.
	 */
	public static class PasteAction extends RecordableTextAction {

 
		public PasteAction() {
			super(DefaultEditorKit.pasteAction);
		}

		public PasteAction(String name, Icon icon, String desc,
					Integer mnemonic, KeyStroke accelerator) {
			super(name, icon, desc, mnemonic, accelerator);
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			textArea.paste();
			textArea.requestFocusInWindow();
		}

		public final String getMacroID() {
			return DefaultEditorKit.pasteAction;
		}

	}


	/**
	 * "Plays back" the last macro recorded.
	 */
	public static class PlaybackLastMacroAction extends RecordableTextAction {

 
		public PlaybackLastMacroAction() {
			super(rtaPlaybackLastMacroAction);
		}

		public PlaybackLastMacroAction(String name, Icon icon,
					String desc, Integer mnemonic, KeyStroke accelerator) {
			super(name, icon, desc, mnemonic, accelerator);
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			textArea.playbackLastMacro();
		}

		public boolean isRecordable() {
			return false; // Don't record macro playbacks.
		}

		public final String getMacroID() {
			return rtaPlaybackLastMacroAction;
		}

	}


    /**
     * Positions the caret at the beginning of the previous word.
     */
    public static class PreviousWordAction extends RecordableTextAction {

 		private boolean select;

		public PreviousWordAction(String name, boolean select) {
			super(name);
			this.select = select;
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {

			int offs = textArea.getCaretPosition();
			boolean failed = false;
			try {

				Element curPara = Utilities.getParagraphElement(textArea, offs);
				offs = getPreviousWord(textArea, offs);
				if(offs < curPara.getStartOffset()) {
					offs = Utilities.getParagraphElement(textArea, offs).
												getEndOffset() - 1;
				}

			} catch (BadLocationException bl) {
				if (offs != 0)
					offs = 0;
				else
					failed = true;
			}

			if (!failed) {
				if (select)
					textArea.moveCaretPosition(offs);
				else
					textArea.setCaretPosition(offs);
			}
			else
				UIManager.getLookAndFeel().provideErrorFeedback(textArea);

		}

		public final String getMacroID() {
			return getName();
		}

		protected int getPreviousWord(RTextArea textArea, int offs)
										throws BadLocationException {
			return Utilities.getPreviousWord(textArea, offs);
		}

	}


	/**
	 * Re-does the last action undone.
	 */
	public static class RedoAction extends RecordableTextAction {

 
		public RedoAction() {
			super(rtaRedoAction);
		}

		public RedoAction(String name, Icon icon, String desc,
					Integer mnemonic, KeyStroke accelerator) {
			super(name, icon, desc, mnemonic, accelerator);
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			if (textArea.isEnabled() && textArea.isEditable()) {
				textArea.redoLastAction();
				textArea.requestFocusInWindow();
			}
		}

		public final String getMacroID() {
			return rtaRedoAction;
		}

	}


	/**
	 * Scrolls the text area one line up or down, without changing
	 * the caret position.
	 */
	public static class ScrollAction extends RecordableTextAction {

		private int delta;

		public ScrollAction(String name, int delta) {
			super(name);
			this.delta = delta;
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			Container parent = textArea.getParent();
			if (parent instanceof JViewport) {
				JViewport viewport = (JViewport)parent;
				Point p = viewport.getViewPosition();
				p.y += delta*textArea.getLineHeight();
				if (p.y<0) {
					p.y = 0;
				}
				else {
					Rectangle viewRect = viewport.getViewRect();
					int visibleEnd = p.y + viewRect.height;
					if (visibleEnd>=textArea.getHeight()) {
						p.y = textArea.getHeight() - viewRect.height;
					}
				}
				viewport.setViewPosition(p);
			}
		}

		public final String getMacroID() {
			return getName();
		}

	}


	/**
	 * Selects the entire document.
	 */
	public static class SelectAllAction extends RecordableTextAction {

 
		public SelectAllAction() {
			super(selectAllAction);
		}

		public SelectAllAction(String name, Icon icon, String desc,
					Integer mnemonic, KeyStroke accelerator) {
			super(name, icon, desc, mnemonic, accelerator);
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			Document doc = textArea.getDocument();
			textArea.setCaretPosition(0);
			textArea.moveCaretPosition(doc.getLength());
		}

		public final String getMacroID() {
			return DefaultEditorKit.selectAllAction;
		}

	}


	/**
	 * Selects the line around the caret.
	 */
	public static class SelectLineAction extends RecordableTextAction {

 		private Action start;
		private Action end;

		public SelectLineAction() {
			super(selectLineAction);
			start = new BeginLineAction("pigdog", false);
			end = new EndLineAction("pigdog", true);
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			start.actionPerformed(e);
			end.actionPerformed(e);
		}

		public final String getMacroID() {
			return DefaultEditorKit.selectLineAction;
		}

	}


	/**
	 * Selects the word around the caret.
	 */
	public static class SelectWordAction extends RecordableTextAction {

 		protected Action start;
		protected Action end;

		public SelectWordAction() {
			super(selectWordAction);
			createActions();
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			start.actionPerformed(e);
			end.actionPerformed(e);
		}

		protected void createActions() {
			start = new BeginWordAction("pigdog", false);
			end = new EndWordAction("pigdog", true);
		}

		public final String getMacroID() {
			return DefaultEditorKit.selectWordAction;
		}

	}


	/**
	 * Puts the text area into read-only mode.
	 */
	public static class SetReadOnlyAction extends RecordableTextAction {

 
		public SetReadOnlyAction() {
			super(readOnlyAction);
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			textArea.setEditable(false);
		}

		public final String getMacroID() {
			return DefaultEditorKit.readOnlyAction;
		}

		public boolean isRecordable() {
			return false; // Why would you want to record this?
		}

	}


	/**
	 * Puts the text area into writable (from read-only) mode.
	 */
	public static class SetWritableAction extends RecordableTextAction {

 
		public SetWritableAction() {
			super(writableAction);
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			textArea.setEditable(true);
		}

		public final String getMacroID() {
			return DefaultEditorKit.writableAction;
		}

		public boolean isRecordable() {
			return false; // Why would you want to record this?
		}

	}


	/**
	 * The action for inserting a time/date stamp.
	 */
	public static class TimeDateAction extends RecordableTextAction {

 
		public TimeDateAction() {
			super(rtaTimeDateAction);
		}

		public TimeDateAction(String name, Icon icon, String desc,
					Integer mnemonic, KeyStroke accelerator) {
			super(name, icon, desc, mnemonic, accelerator);
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			if (!textArea.isEditable() || !textArea.isEnabled()) {
				UIManager.getLookAndFeel().provideErrorFeedback(textArea);
				return;
			}
			Date today = new Date();
			DateFormat timeDateStamp = DateFormat.getDateTimeInstance();
			String dateString = timeDateStamp.format(today);
			textArea.replaceSelection(dateString);
		}

		public final String getMacroID() {
			return rtaTimeDateAction;
		}

	}


	/**
	 * Toggles whether the current line has a bookmark.
	 */
	public static class ToggleBookmarkAction extends RecordableTextAction {
 
		public ToggleBookmarkAction() {
			super(rtaToggleBookmarkAction);
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			Container parent = textArea.getParent();
			if (parent instanceof JViewport) {
				parent = parent.getParent();
				if (parent instanceof RTextScrollPane) {
					RTextScrollPane sp = (RTextScrollPane)parent;
					Gutter gutter = sp.getGutter();
					int line = textArea.getCaretLineNumber();
					try {
						gutter.toggleBookmark(line);
					} catch (BadLocationException ble) { // Never happens
						UIManager.getLookAndFeel().
									provideErrorFeedback(textArea);
						ble.printStackTrace();
					}
				}
			}
		}

		public final String getMacroID() {
			return rtaToggleBookmarkAction;
		}

	}


	/**
	 * The action for the insert key toggling insert/overwrite modes.
	 */
	public static class ToggleTextModeAction extends RecordableTextAction {
 
		public ToggleTextModeAction() {
			super(rtaToggleTextModeAction);
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			int textMode = textArea.getTextMode();
			if (textMode==RTextArea.INSERT_MODE)
				textArea.setTextMode(RTextArea.OVERWRITE_MODE);
			else
				textArea.setTextMode(RTextArea.INSERT_MODE);
		}

		public final String getMacroID() {
			return rtaToggleTextModeAction;
		}

	}


	/**
	 * Undoes the last action done.
	 */
	public static class UndoAction extends RecordableTextAction {

 		public UndoAction() {
			super(rtaUndoAction);
		}

		public UndoAction(String name, Icon icon, String desc,
					Integer mnemonic, KeyStroke accelerator) {
			super(name, icon, desc, mnemonic, accelerator);
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			if (textArea.isEnabled() && textArea.isEditable()) {
				textArea.undoLastAction();
				textArea.requestFocusInWindow();
			}
		}

		public final String getMacroID() {
			return rtaUndoAction;
		}

	}


	/**
	 * Removes the selection, if any.
	 */
	public static class UnselectAction extends RecordableTextAction {

 
		public UnselectAction() {
			super(rtaUnselectAction);
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			textArea.setCaretPosition(textArea.getCaretPosition());
		}

		public final String getMacroID() {
			return rtaUnselectAction;
		}

	}

 
	/**
	 * Action to make the selection upper-case.
	 */
	public static class UpperSelectionCaseAction extends RecordableTextAction {

 
		public UpperSelectionCaseAction() {
			super(rtaUpperSelectionCaseAction);
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			if (!textArea.isEditable() || !textArea.isEnabled()) {
				UIManager.getLookAndFeel().provideErrorFeedback(textArea);
				return;
			}
			String selection = textArea.getSelectedText();
			if (selection!=null)
				textArea.replaceSelection(selection.toUpperCase());
			textArea.requestFocusInWindow();
		}

		public final String getMacroID() {
			return getName();
		}

	}


	/**
	 * Scrolls up/down vertically.  The select version of this action extends
	 * the selection, instead of simply moving the caret.
	 */
	public static class VerticalPageAction extends RecordableTextAction {

		private boolean select;
		private int direction;

		public VerticalPageAction(String name, int direction, boolean select) {
			super(name);
			this.select = select;
			this.direction = direction;
		}

		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {

			Rectangle visible = textArea.getVisibleRect();
			Rectangle newVis = new Rectangle(visible);
			int selectedIndex = textArea.getCaretPosition();
			int scrollAmount = textArea.getScrollableBlockIncrement(
							visible, SwingConstants.VERTICAL, direction); 
			int initialY = visible.y;
			Caret caret = textArea.getCaret();
			Point magicPosition = caret.getMagicCaretPosition();
			int yOffset;   

			if (selectedIndex!=-1) {

				try {

					Rectangle dotBounds = textArea.modelToView(selectedIndex);
					int x = (magicPosition != null) ? magicPosition.x :
												dotBounds.x;
					int h = dotBounds.height;
					yOffset = direction *
							((int)Math.ceil(scrollAmount/(double)h)-1)*h; 
					newVis.y = constrainY(textArea, initialY+yOffset, yOffset, visible.height);                        
					int newIndex;

					if (visible.contains(dotBounds.x, dotBounds.y)) {
						// Dot is currently visible, base the new
						// location off the old, or
						newIndex = textArea.viewToModel(
									new Point(x, constrainY(textArea,
										dotBounds.y + yOffset, 0, 0)));
										}
					else {
						// Dot isn't visible, choose the top or the bottom
						// for the new location.
						if (direction == -1) {
							newIndex = textArea.viewToModel(new Point(
													x, newVis.y));
						}
						else {
							newIndex = textArea.viewToModel(new Point(
									x, newVis.y + visible.height));
						}
					}
					newIndex = constrainOffset(textArea, newIndex);
					if (newIndex != selectedIndex) {
						// Make sure the new visible location contains
						// the location of dot, otherwise Caret will
						// cause an additional scroll.
						adjustScrollIfNecessary(textArea, newVis, initialY,
											newIndex);
						if (select)
							textArea.moveCaretPosition(newIndex);
						else
							textArea.setCaretPosition(newIndex);
					}

				} catch (BadLocationException ble) { }

			} // End of if (selectedIndex!=-1).

			else {
				yOffset = direction * scrollAmount;
				newVis.y = constrainY(textArea, initialY + yOffset, yOffset, visible.height);
			}

			if (magicPosition != null)
				caret.setMagicCaretPosition(magicPosition);

			textArea.scrollRectToVisible(newVis);
		}

		private int constrainY(JTextComponent textArea, int y, int vis, int screenHeight) {
			if (y < 0)
				y = 0;
			else if (y + vis > textArea.getHeight()) {
				//y = Math.max(0, textArea.getHeight() - vis);
				y = Math.max(0, textArea.getHeight()-screenHeight);
			}
			return y;
		}

		private int constrainOffset(JTextComponent text, int offset) {
			Document doc = text.getDocument();
			if ((offset != 0) && (offset > doc.getLength()))
				offset = doc.getLength();
			if (offset  < 0)
				offset = 0;
			return offset;
		}

		private void adjustScrollIfNecessary(JTextComponent text,
									Rectangle visible, int initialY,
									int index) {
			try {
				Rectangle dotBounds = text.modelToView(index);
	                if (dotBounds.y < visible.y ||
					(dotBounds.y > (visible.y + visible.height)) ||
					(dotBounds.y + dotBounds.height) >
					(visible.y + visible.height)) {
					int y;
					if (dotBounds.y < visible.y)
						y = dotBounds.y;
					else
						y = dotBounds.y + dotBounds.height - visible.height;
					if ((direction == -1 && y < initialY) ||
						(direction == 1 && y > initialY))
						// Only adjust if won't cause scrolling upward.
						visible.y = y;
				}
			} catch (BadLocationException ble) {}
		}

		public final String getMacroID() {
			return getName();
		}

	}


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy