mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 19:28:01 +00:00
Create FUndoManager to encapsulate making undo/redo logic smarter and lumping typing changes together
This commit is contained in:
@@ -1,24 +1,17 @@
|
||||
package forge.gui.toolbox;
|
||||
|
||||
import java.awt.Insets;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.event.UndoableEditEvent;
|
||||
import javax.swing.event.UndoableEditListener;
|
||||
import javax.swing.undo.CannotRedoException;
|
||||
import javax.swing.undo.CannotUndoException;
|
||||
import javax.swing.undo.UndoManager;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class FTextEditor extends JScrollPane {
|
||||
private final JTextArea tarEditor;
|
||||
private final FUndoManager undoManager;
|
||||
|
||||
public FTextEditor() {
|
||||
tarEditor = new JTextArea();
|
||||
@@ -28,8 +21,9 @@ public class FTextEditor extends JScrollPane {
|
||||
skin.setBackground(FSkin.getColor(FSkin.Colors.CLR_THEME2));
|
||||
skin.setCaretColor(FSkin.getColor(FSkin.Colors.CLR_TEXT));
|
||||
|
||||
undoManager = new FUndoManager(tarEditor);
|
||||
|
||||
tarEditor.setMargin(new Insets(3, 3, 3, 3));
|
||||
tarEditor.getDocument().addUndoableEditListener(new MyUndoableEditListener());
|
||||
tarEditor.addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
@@ -37,15 +31,15 @@ public class FTextEditor extends JScrollPane {
|
||||
switch (e.getKeyCode()) {
|
||||
case KeyEvent.VK_Z:
|
||||
if (e.isShiftDown()) {
|
||||
redoAction.actionPerformed(null);
|
||||
undoManager.redo();
|
||||
}
|
||||
else {
|
||||
undoAction.actionPerformed(null);
|
||||
undoManager.undo();
|
||||
}
|
||||
break;
|
||||
case KeyEvent.VK_Y:
|
||||
if (!e.isShiftDown()) {
|
||||
redoAction.actionPerformed(null);
|
||||
undoManager.redo();
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -89,73 +83,4 @@ public class FTextEditor extends JScrollPane {
|
||||
public void addDocumentListener(DocumentListener listener) {
|
||||
tarEditor.getDocument().addDocumentListener(listener);
|
||||
}
|
||||
|
||||
//Undo/Redo
|
||||
private UndoAction undoAction = new UndoAction();
|
||||
private RedoAction redoAction = new RedoAction();
|
||||
private UndoManager undoManager = new UndoManager();
|
||||
|
||||
private class MyUndoableEditListener implements UndoableEditListener {
|
||||
public void undoableEditHappened(UndoableEditEvent e) {
|
||||
undoManager.addEdit(e.getEdit());
|
||||
undoAction.updateUndoState();
|
||||
redoAction.updateRedoState();
|
||||
}
|
||||
}
|
||||
|
||||
private class UndoAction extends AbstractAction {
|
||||
public UndoAction() {
|
||||
super("Undo");
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
try {
|
||||
undoManager.undo();
|
||||
} catch (CannotUndoException ex) {
|
||||
System.out.println("Unable to undo: " + ex);
|
||||
ex.printStackTrace();
|
||||
}
|
||||
updateUndoState();
|
||||
redoAction.updateRedoState();
|
||||
}
|
||||
|
||||
protected void updateUndoState() {
|
||||
if (undoManager.canUndo()) {
|
||||
setEnabled(true);
|
||||
putValue(Action.NAME, undoManager.getUndoPresentationName());
|
||||
} else {
|
||||
setEnabled(false);
|
||||
putValue(Action.NAME, "Undo");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RedoAction extends AbstractAction {
|
||||
public RedoAction() {
|
||||
super("Redo");
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
try {
|
||||
undoManager.redo();
|
||||
} catch (CannotRedoException ex) {
|
||||
System.out.println("Unable to redo: " + ex);
|
||||
ex.printStackTrace();
|
||||
}
|
||||
updateRedoState();
|
||||
undoAction.updateUndoState();
|
||||
}
|
||||
|
||||
protected void updateRedoState() {
|
||||
if (undoManager.canRedo()) {
|
||||
setEnabled(true);
|
||||
putValue(Action.NAME, undoManager.getRedoPresentationName());
|
||||
} else {
|
||||
setEnabled(false);
|
||||
putValue(Action.NAME, "Redo");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
250
forge-gui/src/main/java/forge/gui/toolbox/FUndoManager.java
Normal file
250
forge-gui/src/main/java/forge/gui/toolbox/FUndoManager.java
Normal file
@@ -0,0 +1,250 @@
|
||||
package forge.gui.toolbox;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.InputEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.KeyStroke;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.event.UndoableEditEvent;
|
||||
import javax.swing.event.UndoableEditListener;
|
||||
import javax.swing.text.AbstractDocument;
|
||||
import javax.swing.text.JTextComponent;
|
||||
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 will merge individual edits into a single larger edit.
|
||||
** That is, characters entered sequentially will be grouped together and
|
||||
** undone as a group. Any attribute changes will be considered as part
|
||||
** of the group and will therefore be undone when the group is undone.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class FUndoManager extends UndoManager
|
||||
implements UndoableEditListener, DocumentListener
|
||||
{
|
||||
private UndoManager undoManager;
|
||||
private CompoundEdit compoundEdit;
|
||||
private JTextComponent textComponent;
|
||||
private UndoAction undoAction;
|
||||
private RedoAction redoAction;
|
||||
|
||||
// These fields are used to help determine whether the edit is an
|
||||
// incremental edit. The offset and length should increase by 1 for
|
||||
// each character added or decrease by 1 for each character removed.
|
||||
|
||||
private int lastOffset;
|
||||
private int lastLength;
|
||||
|
||||
public FUndoManager(JTextComponent textComponent) {
|
||||
this.textComponent = textComponent;
|
||||
undoManager = this;
|
||||
undoAction = new UndoAction();
|
||||
redoAction = new RedoAction();
|
||||
textComponent.getDocument().addUndoableEditListener(this);
|
||||
}
|
||||
|
||||
/*
|
||||
** Add a DocumentLister before the undo is done so we can position
|
||||
** the Caret correctly as each edit is undone.
|
||||
*/
|
||||
public void undo() {
|
||||
if (canUndo()) {
|
||||
textComponent.getDocument().addDocumentListener(this);
|
||||
super.undo();
|
||||
textComponent.getDocument().removeDocumentListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Add a DocumentLister before the redo is done so we can position
|
||||
** the Caret correctly as each edit is redone.
|
||||
*/
|
||||
public void redo() {
|
||||
if (canRedo()) {
|
||||
textComponent.getDocument().addDocumentListener(this);
|
||||
super.redo();
|
||||
textComponent.getDocument().removeDocumentListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Whenever an UndoableEdit happens the edit will either be absorbed
|
||||
** by the current compound edit or a new compound edit will be started
|
||||
*/
|
||||
public void undoableEditHappened(UndoableEditEvent e) {
|
||||
// Start a new compound edit
|
||||
if (compoundEdit == null) {
|
||||
compoundEdit = startCompoundEdit(e.getEdit());
|
||||
return;
|
||||
}
|
||||
|
||||
int offsetChange = textComponent.getCaretPosition() - lastOffset;
|
||||
int lengthChange = textComponent.getDocument().getLength() - lastLength;
|
||||
|
||||
// Check for an attribute change
|
||||
AbstractDocument.DefaultDocumentEvent event = (AbstractDocument.DefaultDocumentEvent)e.getEdit();
|
||||
|
||||
if (event.getType().equals(DocumentEvent.EventType.CHANGE)) {
|
||||
if (offsetChange == 0) {
|
||||
compoundEdit.addEdit(e.getEdit());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for an incremental edit or backspace.
|
||||
// The Change in Caret position and Document length should both be
|
||||
// either 1 or -1.
|
||||
if (offsetChange == lengthChange && Math.abs(offsetChange) == 1) {
|
||||
compoundEdit.addEdit(e.getEdit());
|
||||
lastOffset = textComponent.getCaretPosition();
|
||||
lastLength = textComponent.getDocument().getLength();
|
||||
return;
|
||||
}
|
||||
|
||||
// Not incremental edit, end previous edit and start a new one
|
||||
compoundEdit.end();
|
||||
compoundEdit = startCompoundEdit(e.getEdit());
|
||||
}
|
||||
|
||||
/*
|
||||
** Each CompoundEdit will store a group of related incremental edits
|
||||
** (ie. each character typed or backspaced is an incremental edit)
|
||||
*/
|
||||
private CompoundEdit startCompoundEdit(UndoableEdit anEdit) {
|
||||
// Track Caret and Document information of this compound edit
|
||||
lastOffset = textComponent.getCaretPosition();
|
||||
lastLength = textComponent.getDocument().getLength();
|
||||
|
||||
// The compound edit is used to store incremental edits
|
||||
compoundEdit = new MyCompoundEdit();
|
||||
compoundEdit.addEdit(anEdit);
|
||||
|
||||
// The compound edit is added to the UndoManager. All incremental
|
||||
// edits stored in the compound edit will be undone/redone at once
|
||||
addEdit(compoundEdit);
|
||||
|
||||
undoAction.updateUndoState();
|
||||
redoAction.updateRedoState();
|
||||
|
||||
return compoundEdit;
|
||||
}
|
||||
|
||||
/*
|
||||
* The Action to Undo changes to the Document.
|
||||
* The state of the Action is managed by the CompoundUndoManager
|
||||
*/
|
||||
public Action getUndoAction() {
|
||||
return undoAction;
|
||||
}
|
||||
|
||||
/*
|
||||
* The Action to Redo changes to the Document.
|
||||
* The state of the Action is managed by the CompoundUndoManager
|
||||
*/
|
||||
public Action getRedoAction() {
|
||||
return redoAction;
|
||||
}
|
||||
|
||||
/*
|
||||
* Updates to the Document as a result of Undo/Redo will cause the
|
||||
* Caret to be repositioned
|
||||
*/
|
||||
public void insertUpdate(final DocumentEvent e) {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
int offset = e.getOffset() + e.getLength();
|
||||
offset = Math.min(offset, textComponent.getDocument().getLength());
|
||||
textComponent.setCaretPosition(offset);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
textComponent.setCaretPosition(e.getOffset());
|
||||
}
|
||||
|
||||
public void changedUpdate(DocumentEvent e) {}
|
||||
|
||||
private class MyCompoundEdit extends CompoundEdit {
|
||||
public boolean isInProgress() {
|
||||
// in order for the canUndo() and canRedo() methods to work
|
||||
// assume that the compound edit is never in progress
|
||||
return false;
|
||||
}
|
||||
|
||||
public void undo() throws CannotUndoException {
|
||||
// End the edit so future edits don't get absorbed by this edit
|
||||
if (compoundEdit != null)
|
||||
compoundEdit.end();
|
||||
|
||||
super.undo();
|
||||
|
||||
// Always start a new compound edit after an undo
|
||||
compoundEdit = null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform the Undo and update the state of the undo/redo Actions
|
||||
*/
|
||||
private class UndoAction extends AbstractAction {
|
||||
public UndoAction() {
|
||||
putValue(Action.NAME, "Undo");
|
||||
putValue(Action.SHORT_DESCRIPTION, getValue(Action.NAME));
|
||||
putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_U));
|
||||
putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_MASK));
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
try {
|
||||
undoManager.undo();
|
||||
textComponent.requestFocusInWindow();
|
||||
}
|
||||
catch (CannotUndoException ex) {}
|
||||
|
||||
updateUndoState();
|
||||
redoAction.updateRedoState();
|
||||
}
|
||||
|
||||
private void updateUndoState() {
|
||||
setEnabled(undoManager.canUndo());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform the Redo and update the state of the undo/redo Actions
|
||||
*/
|
||||
private class RedoAction extends AbstractAction {
|
||||
public RedoAction() {
|
||||
putValue(Action.NAME, "Redo");
|
||||
putValue(Action.SHORT_DESCRIPTION, getValue(Action.NAME));
|
||||
putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_R));
|
||||
putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_MASK));
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
try {
|
||||
undoManager.redo();
|
||||
textComponent.requestFocusInWindow();
|
||||
}
|
||||
catch (CannotRedoException ex) {}
|
||||
|
||||
updateRedoState();
|
||||
undoAction.updateUndoState();
|
||||
}
|
||||
|
||||
protected void updateRedoState() {
|
||||
setEnabled(undoManager.canRedo());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user