-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathCompoundUndoManager.java
More file actions
261 lines (219 loc) · 6.68 KB
/
CompoundUndoManager.java
File metadata and controls
261 lines (219 loc) · 6.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.event.*;
import javax.swing.undo.*;
/*
** 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)
{
UndoableEdit ue = e.getEdit();
// 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 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;
}
//
// 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, Integer.valueOf(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, Integer.valueOf(KeyEvent.VK_R) );
putValue( Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("Control Y") );
setEnabled(false);
}
public void actionPerformed(ActionEvent e)
{
try
{
undoManager.redo();
textComponent.requestFocusInWindow();
}
catch (CannotRedoException ex) {}
updateRedoState();
undoAction.updateUndoState();
}
protected void updateRedoState()
{
setEnabled( undoManager.canRedo() );
}
}
}