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.
 *
 * This library is distributed under a modified BSD license.  See the included
 * RSyntaxTextArea.License.txt file for details.
 */
package org.fife.ui.rtextarea;

import java.awt.Container;
import java.awt.Font;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.io.Reader;
import java.text.BreakIterator;
import java.text.DateFormat;
import java.util.Date;

import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JViewport;
import javax.swing.KeyStroke;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.text.BadLocationException;
import javax.swing.text.Caret;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
import javax.swing.text.NavigationFilter;
import javax.swing.text.Position;
import javax.swing.text.Segment;
import javax.swing.text.Utilities;

import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities;


/**
 * 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!
@SuppressWarnings({ "checkstyle:constantname" })
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 select the next occurrence of the selected text.
	 */
	public static final String rtaNextOccurrenceAction		= "RTA.NextOccurrenceAction";

	/**
     * Action to select the previous occurrence of the selected text.
	 */
	public static final String rtaPrevOccurrenceAction		= "RTA.PrevOccurrenceAction";

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

	/**
	 * Action to display the paste history popup.
	 */
	public static final String clipboardHistoryAction		= "RTA.PasteHistoryAction";

	/**
	 * 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 ClipboardHistoryAction(),
		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 NextOccurrenceAction(rtaNextOccurrenceAction),
        new PreviousOccurrenceAction(rtaPrevOccurrenceAction),
		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();
	}


	/**
	 * Creates an icon row header to use in the gutter for a text area.
	 *
	 * @param textArea The text area.
	 * @return The icon row header.
	 */
	public IconRowHeader createIconRowHeader(RTextArea textArea) {
		return new IconRowHeader(textArea);
	}


	/**
	 * Creates a line number list to use in the gutter for a text area.
	 *
	 * @param textArea The text area.
	 * @return The line number list.
	 */
	public LineNumberList createLineNumberList(RTextArea textArea) {
		return new LineNumberList(textArea);
	}


	/**
	 * 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
	 */
	@Override
	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.
	*/
	@Override
	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);
		}

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

		@Override
		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;
		}

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

		@Override
		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;
		}

		@Override
		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 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();
		}

		@Override
		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();
		}

		@Override
		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);
		}

		@Override
		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);
		}

		@Override
		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
			}

		}

		@Override
		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);
		}

		@Override
		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);
			}
            if (textArea != null) {
                textArea.requestFocusInWindow();
            }

		}

		@Override
		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);
		}

		@Override
		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);
			}

		}

		@Override
		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);
		}

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

		@Override
		public String getMacroID() {
			return rtaDeletePrevWordAction;
		}

		/**
		 * Returns the starting offset to delete.  Exists so subclasses can
		 * override.
		 */
		protected int getPreviousWordStart(RTextArea textArea, int end)
				throws BadLocationException {
			return Utilities.getPreviousWord(textArea, end);
		}

	}


	/**
	 * 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);
		}

		@Override
		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 = 0;
					try {
						wordStart = getPreviousWord(textArea, searchOffs);
					} catch (BadLocationException ble) {
						// No more words.  Sometimes happens for example if the
						// document starts off with whitespace - then searchOffs
						// is > 0 but there are no more words
						wordStart = BreakIterator.DONE;
					}
					if (wordStart==BreakIterator.DONE) {
						UIManager.getLookAndFeel().provideErrorFeedback(
								textArea);
						break;
					}
					int end = 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();
			}

		}

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

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

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

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

		/**
		 * Utilities.getWordStart() treats spans of whitespace and
		 * single non-letter chars as "words."  This method is used to filter
		 * that kind of thing out - non-words should not be suggested by this
		 * action.
		 *
		 * @param prefix The prefix characters before the caret.
		 * @return Whether the prefix could be part of a "word" in the context
		 *         of the text area's current content.
		 */
		protected boolean isAcceptablePrefix(String prefix) {
			return prefix.length() > 0 &&
				Character.isLetter(prefix.charAt(prefix.length()-1));
		}

	}


	/**
	 * 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;
		}

		@Override
		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			int dot = getVisibleEnd(textArea);
			if (select) {
				textArea.moveCaretPosition(dot);
			}
			else {
				textArea.setCaretPosition(dot);
			}
		}

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

		protected int getVisibleEnd(RTextArea textArea) {
			return textArea.getDocument().getLength();
		}

	}


	/**
	 * 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;
		}

		@Override
		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			int offs = textArea.getCaretPosition();
			int endOffs = 0;
			try {
				if (textArea.getLineWrap()) {
					// Must check per character, since one logical line may be
					// many physical lines.
					// FIXME:  Replace Utilities call with custom version to
					// cut down on all of the modelToViews, as each call causes
					// a getTokenList => expensive!
					endOffs = Utilities.getRowEnd(textArea, offs);
				}
				else {
					Element root = textArea.getDocument().getDefaultRootElement();
					int line = root.getElementIndex(offs);
					endOffs = root.getElement(line).getEndOffset() - 1;
				}
				if (select) {
					textArea.moveCaretPosition(endOffs);
				}
				else {
					textArea.setCaretPosition(endOffs);
				}
			} catch (Exception ex) {
				UIManager.getLookAndFeel().provideErrorFeedback(textArea);
			}
		}

		@Override
		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);
		}

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

		@Override
		public final String getMacroID() {
			return rtaEndRecordingMacroAction;
		}

		@Override
		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;
		}

		@Override
		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);
			}
		}

		@Override
		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();
		}

		@Override
		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);
			}

		}

		@Override
		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;

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

		@Override
		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);
				}
			}

		}

		@Override
		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);
		}

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

		@Override
		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);
		}

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

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

		@Override
		public final String getMacroID() {
			return rtaPlaybackLastMacroAction;
		}

	}


	/**
	 * Select the previous occurrence of the text last selected.
	 */
	public static class PreviousOccurrenceAction extends RecordableTextAction {

		public PreviousOccurrenceAction(String name) {
			super(name);
		}

		@Override
		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			String selectedText = textArea.getSelectedText();
			if (selectedText == null || selectedText.length() == 0) {
				selectedText = RTextArea.getSelectedOccurrenceText();
				if (selectedText == null || selectedText.length() == 0) {
					UIManager.getLookAndFeel().provideErrorFeedback(textArea);
					return;
				}
			}
			SearchContext context = new SearchContext(selectedText);
			if (!textArea.getMarkAllOnOccurrenceSearches()) {
				context.setMarkAll(false);
			}
			context.setSearchForward(false);
			if (!SearchEngine.find(textArea, context).wasFound()) {
				UIManager.getLookAndFeel().provideErrorFeedback(textArea);
			}
			RTextArea.setSelectedOccurrenceText(selectedText);
		}

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

	}


    /**
     * 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;
		}

		@Override
		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);
			}

		}

		@Override
		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);
		}

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

		@Override
		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;
		}

		@Override
		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);
			}
		}

		@Override
		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);
		}

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

		@Override
		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);
		}

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

		@Override
		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();
		}

		@Override
		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);
		}

		@Override
		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);
		}

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

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

		@Override
		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);
		}

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

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

		@Override
		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);
		}

		@Override
		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);
		}

		@Override
		public final String getMacroID() {
			return rtaTimeDateAction;
		}

	}


	/**
	 * Toggles whether the current line has a bookmark.
	 */
	public static class ToggleBookmarkAction extends RecordableTextAction {

		public ToggleBookmarkAction() {
			super(rtaToggleBookmarkAction);
		}

		@Override
		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
			Gutter gutter = RSyntaxUtilities.getGutter(textArea);
			if (gutter!=null) {
				int line = textArea.getCaretLineNumber();
				try {
					gutter.toggleBookmark(line);
				} catch (BadLocationException ble) { // Never happens
					UIManager.getLookAndFeel().
								provideErrorFeedback(textArea);
					ble.printStackTrace();
				}
			}
		}

		@Override
		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);
		}

		@Override
		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);
			}
		}

		@Override
		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);
		}

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

		@Override
		public final String getMacroID() {
			return rtaUndoAction;
		}

	}


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

		public UnselectAction() {
			super(rtaUnselectAction);
		}

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

		@Override
		public final String getMacroID() {
			return rtaUnselectAction;
		}

	}


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

		public UpperSelectionCaseAction() {
			super(rtaUpperSelectionCaseAction);
		}

		@Override
		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();
		}

		@Override
		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;
		}

		@Override
		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) {}
		}

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

	}


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy