mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 03:38:01 +00:00
Create FUndoManager to encapsulate making undo/redo logic smarter and lumping typing changes together
This commit is contained in:
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -15414,6 +15414,7 @@ forge-gui/src/main/java/forge/gui/toolbox/FTabbedPane.java -text
|
|||||||
forge-gui/src/main/java/forge/gui/toolbox/FTextArea.java -text
|
forge-gui/src/main/java/forge/gui/toolbox/FTextArea.java -text
|
||||||
forge-gui/src/main/java/forge/gui/toolbox/FTextEditor.java -text
|
forge-gui/src/main/java/forge/gui/toolbox/FTextEditor.java -text
|
||||||
forge-gui/src/main/java/forge/gui/toolbox/FTextField.java -text
|
forge-gui/src/main/java/forge/gui/toolbox/FTextField.java -text
|
||||||
|
forge-gui/src/main/java/forge/gui/toolbox/FUndoManager.java -text
|
||||||
forge-gui/src/main/java/forge/gui/toolbox/LayoutHelper.java -text
|
forge-gui/src/main/java/forge/gui/toolbox/LayoutHelper.java -text
|
||||||
forge-gui/src/main/java/forge/gui/toolbox/SaveOpenDialog.java -text
|
forge-gui/src/main/java/forge/gui/toolbox/SaveOpenDialog.java -text
|
||||||
forge-gui/src/main/java/forge/gui/toolbox/imaging/FImagePanel.java -text
|
forge-gui/src/main/java/forge/gui/toolbox/imaging/FImagePanel.java -text
|
||||||
|
|||||||
@@ -1,24 +1,17 @@
|
|||||||
package forge.gui.toolbox;
|
package forge.gui.toolbox;
|
||||||
|
|
||||||
import java.awt.Insets;
|
import java.awt.Insets;
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.awt.event.KeyAdapter;
|
import java.awt.event.KeyAdapter;
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
|
|
||||||
import javax.swing.AbstractAction;
|
|
||||||
import javax.swing.Action;
|
|
||||||
import javax.swing.JScrollPane;
|
import javax.swing.JScrollPane;
|
||||||
import javax.swing.JTextArea;
|
import javax.swing.JTextArea;
|
||||||
import javax.swing.event.DocumentListener;
|
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")
|
@SuppressWarnings("serial")
|
||||||
public class FTextEditor extends JScrollPane {
|
public class FTextEditor extends JScrollPane {
|
||||||
private final JTextArea tarEditor;
|
private final JTextArea tarEditor;
|
||||||
|
private final FUndoManager undoManager;
|
||||||
|
|
||||||
public FTextEditor() {
|
public FTextEditor() {
|
||||||
tarEditor = new JTextArea();
|
tarEditor = new JTextArea();
|
||||||
@@ -28,8 +21,9 @@ public class FTextEditor extends JScrollPane {
|
|||||||
skin.setBackground(FSkin.getColor(FSkin.Colors.CLR_THEME2));
|
skin.setBackground(FSkin.getColor(FSkin.Colors.CLR_THEME2));
|
||||||
skin.setCaretColor(FSkin.getColor(FSkin.Colors.CLR_TEXT));
|
skin.setCaretColor(FSkin.getColor(FSkin.Colors.CLR_TEXT));
|
||||||
|
|
||||||
|
undoManager = new FUndoManager(tarEditor);
|
||||||
|
|
||||||
tarEditor.setMargin(new Insets(3, 3, 3, 3));
|
tarEditor.setMargin(new Insets(3, 3, 3, 3));
|
||||||
tarEditor.getDocument().addUndoableEditListener(new MyUndoableEditListener());
|
|
||||||
tarEditor.addKeyListener(new KeyAdapter() {
|
tarEditor.addKeyListener(new KeyAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void keyPressed(KeyEvent e) {
|
public void keyPressed(KeyEvent e) {
|
||||||
@@ -37,15 +31,15 @@ public class FTextEditor extends JScrollPane {
|
|||||||
switch (e.getKeyCode()) {
|
switch (e.getKeyCode()) {
|
||||||
case KeyEvent.VK_Z:
|
case KeyEvent.VK_Z:
|
||||||
if (e.isShiftDown()) {
|
if (e.isShiftDown()) {
|
||||||
redoAction.actionPerformed(null);
|
undoManager.redo();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
undoAction.actionPerformed(null);
|
undoManager.undo();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case KeyEvent.VK_Y:
|
case KeyEvent.VK_Y:
|
||||||
if (!e.isShiftDown()) {
|
if (!e.isShiftDown()) {
|
||||||
redoAction.actionPerformed(null);
|
undoManager.redo();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -89,73 +83,4 @@ public class FTextEditor extends JScrollPane {
|
|||||||
public void addDocumentListener(DocumentListener listener) {
|
public void addDocumentListener(DocumentListener listener) {
|
||||||
tarEditor.getDocument().addDocumentListener(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 java.io.PrintWriter;
|
||||||
|
|
||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
import javax.swing.JTextArea;
|
|
||||||
import javax.swing.event.DocumentEvent;
|
import javax.swing.event.DocumentEvent;
|
||||||
import javax.swing.event.DocumentListener;
|
import javax.swing.event.DocumentListener;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user