/* * 12/06/2008 * * RUndoManager.java - Handles undo/redo behavior for RTextArea. * Copyright (C) 2008 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.util.ResourceBundle; import javax.swing.Action; import javax.swing.UIManager; import javax.swing.event.UndoableEditEvent; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import javax.swing.undo.CompoundEdit; import javax.swing.undo.UndoManager; import javax.swing.undo.UndoableEdit; /** * This class manages undos/redos for a particular editor pane. It groups all undos that occur one character position * apart together, to avoid Java's horrible "one character at a time" undo behavior. It also recognizes "replace" * actions (i.e., text is selected, then the user types), and treats it as a single action, instead of a remove/insert * action pair. * * @author Robert Futrell * @version 1.0 */ class RUndoManager extends UndoManager { public RCompoundEdit compoundEdit; private RTextArea textArea; private int lastOffset; private String cantUndoText; private String cantRedoText; private int internalAtomicEditDepth; private static final String MSG = "org.fife.ui.rtextarea.RTextArea"; /** * Constructor. * * @param textArea * The parent text area. */ public RUndoManager(RTextArea textArea) { this.textArea = textArea; ResourceBundle msg = ResourceBundle.getBundle(MSG); cantUndoText = msg.getString("CantUndoName"); cantRedoText = msg.getString("CantRedoName"); } /** * Begins an "atomic" edit. This method is called when RTextArea KNOWS that some edits should be compound * automatically, such as when the user is typing in overwrite mode (the deletion of the current char + insertion of * the new one) or the playing back of a macro. * * @see #endInternalAtomicEdit() */ public void beginInternalAtomicEdit() { if (++internalAtomicEditDepth == 1) { if (compoundEdit != null) compoundEdit.end(); compoundEdit = new RCompoundEdit(); } } /** * Ends an "atomic" edit. * * @see #beginInternalAtomicEdit() */ public void endInternalAtomicEdit() { if (internalAtomicEditDepth > 0 && --internalAtomicEditDepth == 0) { addEdit(compoundEdit); compoundEdit.end(); compoundEdit = null; updateActions(); // Needed to show the new display name. } } /** * Returns the localized "Can't Redo" string. * * @return The localized "Can't Redo" string. * @see #getCantUndoText() */ public String getCantRedoText() { return cantRedoText; } /** * Returns the localized "Can't Undo" string. * * @return The localized "Can't Undo" string. * @see #getCantRedoText() */ public String getCantUndoText() { return cantUndoText; } /** * {@inheritDoc} */ public void redo() throws CannotRedoException { super.redo(); updateActions(); } private RCompoundEdit startCompoundEdit(UndoableEdit edit) { lastOffset = textArea.getCaretPosition(); compoundEdit = new RCompoundEdit(); compoundEdit.addEdit(edit); addEdit(compoundEdit); return compoundEdit; } /** * {@inheritDoc} */ public void undo() throws CannotUndoException { super.undo(); updateActions(); } public void undoableEditHappened(UndoableEditEvent e) { // This happens when the first undoable edit occurs, and // just after an undo. So, we need to update our actions. if (compoundEdit == null) { compoundEdit = startCompoundEdit(e.getEdit()); updateActions(); return; } else if (internalAtomicEditDepth > 0) { compoundEdit.addEdit(e.getEdit()); return; } // This happens when there's already an undo that has occurred. // Test to see if these undos are on back-to-back characters, // and if they are, group them as a single edit. Since an // undo has already occurred, there is no need to update our // actions here. int diff = textArea.getCaretPosition() - lastOffset; // "<=1" allows contiguous "overwrite mode" key presses to be // grouped together. if (Math.abs(diff) <= 1) {// ==1) { compoundEdit.addEdit(e.getEdit()); lastOffset += diff; // updateActions(); return; } // This happens when this UndoableEdit didn't occur at the // character just after the previous undlabeledit. Since an // undo has already occurred, there is no need to update our // actions here either. compoundEdit.end(); compoundEdit = startCompoundEdit(e.getEdit()); // updateActions(); } /** * Ensures that undo/redo actions are enabled appropriately and have descriptive text at all times. */ public void updateActions() { String text; Action a = RTextArea.getAction(RTextArea.UNDO_ACTION); if (canUndo()) { a.setEnabled(true); text = getUndoPresentationName(); a.putValue(Action.NAME, text); a.putValue(Action.SHORT_DESCRIPTION, text); } else { if (a.isEnabled()) { a.setEnabled(false); text = cantUndoText; a.putValue(Action.NAME, text); a.putValue(Action.SHORT_DESCRIPTION, text); } } a = RTextArea.getAction(RTextArea.REDO_ACTION); if (canRedo()) { a.setEnabled(true); text = getRedoPresentationName(); a.putValue(Action.NAME, text); a.putValue(Action.SHORT_DESCRIPTION, text); } else { if (a.isEnabled()) { a.setEnabled(false); text = cantRedoText; a.putValue(Action.NAME, text); a.putValue(Action.SHORT_DESCRIPTION, text); } } } /** * The action used by {@link RUndoManager}. * * @author Robert Futrell * @version 1.0 */ class RCompoundEdit extends CompoundEdit { public String getUndoPresentationName() { return UIManager.getString("AbstractUndoableEdit.undoText"); } public String getRedoPresentationName() { return UIManager.getString("AbstractUndoableEdit.redoText"); } public boolean isInProgress() { return false; } public void undo() throws CannotUndoException { if (compoundEdit != null) compoundEdit.end(); super.undo(); compoundEdit = null; } } }