org.fife.ui.rtextarea.RUndoManager Maven / Gradle / Ivy
The newest version!
/*
* 12/06/2008
*
* RUndoManager.java - Handles undo/redo behavior for 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.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
*/
public class RUndoManager extends UndoManager {
private 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("Action.CantUndo.Name");
cantRedoText = msg.getString("Action.CantRedo.Name");
}
/**
* 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}
*/
@Override
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}
*/
@Override
public void undo() throws CannotUndoException {
super.undo();
updateActions();
}
@Override
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 edit used by {@link RUndoManager}.
*/
class RCompoundEdit extends CompoundEdit {
@Override
public String getUndoPresentationName() {
return UIManager.getString("AbstractUndoableEdit.undoText");
}
@Override
public String getRedoPresentationName() {
return UIManager.getString("AbstractUndoableEdit.redoText");
}
@Override
public boolean isInProgress() {
return false;
}
@Override
public void undo() throws CannotUndoException {
if (compoundEdit!=null)
compoundEdit.end();
super.undo();
compoundEdit = null;
}
}
}