fix: undo manager works properly

This commit is contained in:
lantzelot-swe 2021-02-12 21:28:50 +01:00
parent 648941706d
commit 71e1b7deff
8 changed files with 387 additions and 21 deletions

View File

@ -1,6 +1,8 @@
package se.lantz;
import java.awt.Dimension;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
@ -32,8 +34,13 @@ public class PCUGameManager
SwingUtilities.invokeLater(() -> {
MainWindow mainWindow = MainWindow.getInstance();
mainWindow.setSize(1400, 850);
mainWindow.setMinimumSize(new Dimension(1300, 700));
GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
int width = gd.getDisplayMode().getWidth();
int height = gd.getDisplayMode().getHeight();
mainWindow.setSize(Math.min(width, 1500), Math.min(height-40, 825));
mainWindow.setMinimumSize(new Dimension(Math.min(width, 1300), Math.min(height-40, 700)));
mainWindow.setVisible(true);
mainWindow.setLocationRelativeTo(null);
mainWindow.initialize();

View File

@ -19,6 +19,7 @@ import javax.swing.event.DocumentListener;
import javax.swing.text.DefaultStyledDocument;
import se.lantz.model.InfoModel;
import se.lantz.util.CustomUndoPlainDocument;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
@ -73,7 +74,7 @@ public class DescriptionPanel extends JPanel
descriptionTextArea.setWrapStyleWord(true);
descriptionTextArea.setLineWrap(true);
DefaultStyledDocument doc = new DefaultStyledDocument();
CustomUndoPlainDocument doc = new CustomUndoPlainDocument();
doc.addDocumentListener(new DocumentListener()
{
@Override

View File

@ -147,7 +147,7 @@ public class GameDetailsBackgroundPanel extends JPanel
{
GridBagLayout gbl_settingsPanel = new GridBagLayout();
settingsPanel = new ScrollablePanel(gbl_settingsPanel);
settingsPanel.setPreferredSize(new Dimension(1120,425));
settingsPanel.setPreferredSize(new Dimension(1120,400));
settingsPanel.setScrollableHeight(ScrollableSizeHint.STRETCH);
settingsPanel.setScrollableWidth(ScrollableSizeHint.STRETCH);
GridBagConstraints gbc_systemPanel = new GridBagConstraints();

View File

@ -26,6 +26,7 @@ import javax.swing.SwingUtilities;
import javax.swing.text.DefaultEditorKit;
import se.lantz.model.InfoModel;
import se.lantz.util.CustomUndoPlainDocument;
import se.lantz.util.TextComponentSupport;
public class InfoPanel extends JPanel
@ -210,6 +211,7 @@ public class InfoPanel extends JPanel
if (titleField == null)
{
titleField = new JTextField();
titleField.setDocument(new CustomUndoPlainDocument());
titleField.addKeyListener(new KeyAdapter()
{
@Override
@ -273,6 +275,7 @@ public class InfoPanel extends JPanel
if (authorField == null)
{
authorField = new JTextField();
authorField.setDocument(new CustomUndoPlainDocument());
authorField.addKeyListener(new KeyAdapter()
{
@Override
@ -291,6 +294,7 @@ public class InfoPanel extends JPanel
if (composerField == null)
{
composerField = new JTextField();
composerField.setDocument(new CustomUndoPlainDocument());
composerField.addKeyListener(new KeyAdapter()
{
@Override

View File

@ -19,6 +19,7 @@ import javax.swing.SwingConstants;
import javax.swing.border.TitledBorder;
import se.lantz.model.JoystickModel;
import se.lantz.util.CustomUndoPlainDocument;
import se.lantz.util.TextComponentSupport;
public class JoystickPanel extends JPanel
@ -256,6 +257,7 @@ public class JoystickPanel extends JPanel
if (configTextField == null)
{
configTextField = new JTextField();
configTextField.setDocument(new CustomUndoPlainDocument());
configTextField.setColumns(10);
configTextField.addFocusListener(new FocusListener()
{

View File

@ -0,0 +1,292 @@
package se.lantz.util;
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.
*/
public class CompoundUndoManager 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 CompoundUndoManager(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()
{
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()
{
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.
// int offsetChange = textComponent.getCaretPosition() - lastOffset;
// int lengthChange = textComponent.getDocument().getLength() - lastLength;
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;
}
//
// Implement DocumentListener
//
/*
* 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)
{
}
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
*/
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("control Z"));
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
*/
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());
}
}
}

View File

@ -0,0 +1,44 @@
package se.lantz.util;
import javax.swing.event.UndoableEditEvent;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;
import javax.swing.undo.CompoundEdit;
/**
* Document supporting undo/redo better than default
*
*/
public class CustomUndoPlainDocument extends PlainDocument
{
private CompoundEdit compoundEdit;
@Override
protected void fireUndoableEditUpdate(UndoableEditEvent e)
{
if (compoundEdit == null)
{
super.fireUndoableEditUpdate(e);
} else
{
compoundEdit.addEdit(e.getEdit());
}
}
@Override
public void replace(int offset, int length, String text, AttributeSet attrs) throws BadLocationException
{
if (length == 0)
{
super.replace(offset, length, text, attrs);
} else
{
compoundEdit = new CompoundEdit();
super.fireUndoableEditUpdate(new UndoableEditEvent(this, compoundEdit));
super.replace(offset, length, text, attrs);
compoundEdit.end();
compoundEdit = null;
}
}
}

View File

@ -3,6 +3,8 @@ package se.lantz.util;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
@ -10,9 +12,12 @@ import javax.swing.AbstractAction;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;
import javax.swing.event.DocumentEvent;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.AbstractDocument.DefaultDocumentEvent;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.TextAction;
import javax.swing.undo.CannotRedoException;
@ -24,7 +29,7 @@ public class TextComponentSupport
{
public final static String UNDO_ACTION = "Undo";
public final static String REDO_ACTION = "Redo";
private static List<UndoManager> managerList = new ArrayList<>();
private TextComponentSupport()
@ -51,17 +56,17 @@ public class TextComponentSupport
addAction(popupMenu, new DefaultEditorKit.CutAction(), KeyEvent.VK_X, "Cut");
addAction(popupMenu, new DefaultEditorKit.CopyAction(), KeyEvent.VK_C, "Copy");
addAction(popupMenu, new DefaultEditorKit.PasteAction(), KeyEvent.VK_V, "Paste");
tc.setComponentPopupMenu(popupMenu);
makeUndoable(popupMenu, tc);
}
}
public static void clearUndoManagers()
{
for (UndoManager undoManager : managerList)
{
undoManager.discardAllEdits();
undoManager.discardAllEdits();
}
}
@ -71,17 +76,26 @@ public class TextComponentSupport
managerList.add(undoMgr);
// Add listener for undoable events
pTextComponent.getDocument().addUndoableEditListener(undoMgr);
// pTextComponent.getDocument().addUndoableEditListener(new UndoableEditListener()
// {
// public void undoableEditHappened(UndoableEditEvent evt)
// {
// if (evt.getEdit().isSignificant())
// pTextComponent.getDocument().addUndoableEditListener(undoMgr);
pTextComponent.getDocument().addUndoableEditListener(new UndoableEditListener()
{
public void undoableEditHappened(UndoableEditEvent evt)
{
// DefaultDocumentEvent event = (DefaultDocumentEvent) evt.getEdit();
//
// System.out.println(
// "EVENT TYPE=" + event.getType() + ", LENGTH=" + event.getLength() + ", OFFSET=" + event.getOffset());
//
// if (event.getType().equals(DocumentEvent.EventType.REMOVE) && event.getLength() > 0 && event.getOffset() == 0)
// {
// undoMgr.addEdit(evt.getEdit());
// //undoMgr.discardAllEdits();
// }
// else
// {
undoMgr.addEdit(evt.getEdit());
// }
// }
// });
}
});
// Add undo/redo actions
AbstractAction undoAction = new AbstractAction(UNDO_ACTION)
@ -93,7 +107,7 @@ public class TextComponentSupport
if (undoMgr.canUndo())
{
undoMgr.undo();
//this.setEnabled(undoMgr.canUndo());
// this.setEnabled(undoMgr.canUndo());
}
} catch (CannotUndoException e)
{
@ -102,7 +116,8 @@ public class TextComponentSupport
}
};
pTextComponent.getActionMap().put(UNDO_ACTION, undoAction);
undoAction.putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_DOWN_MASK));
undoAction.putValue(AbstractAction.ACCELERATOR_KEY,
KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_DOWN_MASK));
AbstractAction redoAction = new AbstractAction(REDO_ACTION)
{
public void actionPerformed(ActionEvent evt)
@ -112,7 +127,7 @@ public class TextComponentSupport
if (undoMgr.canRedo())
{
undoMgr.redo();
//this.setEnabled(undoMgr.canRedo());
// this.setEnabled(undoMgr.canRedo());
}
} catch (CannotRedoException e)
{
@ -121,7 +136,8 @@ public class TextComponentSupport
}
};
pTextComponent.getActionMap().put(REDO_ACTION, redoAction);
redoAction.putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_DOWN_MASK));
redoAction.putValue(AbstractAction.ACCELERATOR_KEY,
KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_DOWN_MASK));
// Create keyboard accelerators for undo/redo actions (Ctrl+Z/Ctrl+Y)
pTextComponent.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_DOWN_MASK), UNDO_ACTION);
pTextComponent.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_DOWN_MASK), REDO_ACTION);