source: josm/trunk/src/org/openstreetmap/josm/data/UndoRedoHandler.java@ 13755

Last change on this file since 13755 was 13739, checked in by Don-vip, 6 years ago

see #16260 - fix spotbugs serialization warnings

  • Property svn:eol-style set to native
File size: 11.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data;
3
4import java.util.EventObject;
5import java.util.Iterator;
6import java.util.LinkedList;
7import java.util.Objects;
8
9import org.openstreetmap.josm.Main;
10import org.openstreetmap.josm.command.Command;
11import org.openstreetmap.josm.data.osm.DataSet;
12import org.openstreetmap.josm.spi.preferences.Config;
13import org.openstreetmap.josm.tools.CheckParameterUtil;
14
15/**
16 * This is the global undo/redo handler for all {@link DataSet}s.
17 * <p>
18 * If you want to change a data set, you can use {@link #add(Command)} to execute a command on it and make that command undoable.
19 */
20public class UndoRedoHandler {
21
22 /**
23 * All commands that were made on the dataset. Don't write from outside!
24 *
25 * @see #getLastCommand()
26 */
27 public final LinkedList<Command> commands = new LinkedList<>();
28 /**
29 * The stack for redoing commands
30 */
31 public final LinkedList<Command> redoCommands = new LinkedList<>();
32
33 private final LinkedList<CommandQueueListener> listenerCommands = new LinkedList<>();
34 private final LinkedList<CommandQueuePreciseListener> preciseListenerCommands = new LinkedList<>();
35
36 /**
37 * Constructs a new {@code UndoRedoHandler}.
38 */
39 public UndoRedoHandler() {
40 // Do nothing
41 }
42
43 /**
44 * A simple listener that gets notified of command queue (undo/redo) size changes.
45 * @see CommandQueuePreciseListener
46 * @since 12718 (moved from {@code OsmDataLayer}
47 */
48 @FunctionalInterface
49 public interface CommandQueueListener {
50 /**
51 * Notifies the listener about the new queue size
52 * @param queueSize Undo stack size
53 * @param redoSize Redo stack size
54 */
55 void commandChanged(int queueSize, int redoSize);
56 }
57
58 /**
59 * A listener that gets notified of command queue (undo/redo) operations individually.
60 * @see CommandQueueListener
61 * @since 13729
62 */
63 public interface CommandQueuePreciseListener {
64
65 /**
66 * Notifies the listener about a new command added to the queue.
67 * @param e event
68 */
69 void commandAdded(CommandAddedEvent e);
70
71 /**
72 * Notifies the listener about commands being cleaned.
73 * @param e event
74 */
75 void cleaned(CommandQueueCleanedEvent e);
76
77 /**
78 * Notifies the listener about a command that has been undone.
79 * @param e event
80 */
81 void commandUndone(CommandUndoneEvent e);
82
83 /**
84 * Notifies the listener about a command that has been redone.
85 * @param e event
86 */
87 void commandRedone(CommandRedoneEvent e);
88 }
89
90 abstract static class CommandQueueEvent extends EventObject {
91 protected CommandQueueEvent(UndoRedoHandler source) {
92 super(Objects.requireNonNull(source));
93 }
94
95 /**
96 * Calls the appropriate method of the listener for this event.
97 * @param listener dataset listener to notify about this event
98 */
99 abstract void fire(CommandQueuePreciseListener listener);
100
101 @Override
102 public final UndoRedoHandler getSource() {
103 return (UndoRedoHandler) super.getSource();
104 }
105 }
106
107 /**
108 * Event fired after a command has been added to the command queue.
109 * @since 13729
110 */
111 public static final class CommandAddedEvent extends CommandQueueEvent {
112
113 private static final long serialVersionUID = 1L;
114 private final Command cmd;
115
116 private CommandAddedEvent(UndoRedoHandler source, Command cmd) {
117 super(source);
118 this.cmd = Objects.requireNonNull(cmd);
119 }
120
121 /**
122 * Returns the added command.
123 * @return the added command
124 */
125 public Command getCommand() {
126 return cmd;
127 }
128
129 @Override
130 void fire(CommandQueuePreciseListener listener) {
131 listener.commandAdded(this);
132 }
133 }
134
135 /**
136 * Event fired after the command queue has been cleaned.
137 * @since 13729
138 */
139 public static final class CommandQueueCleanedEvent extends CommandQueueEvent {
140
141 private static final long serialVersionUID = 1L;
142 private final DataSet ds;
143
144 private CommandQueueCleanedEvent(UndoRedoHandler source, DataSet ds) {
145 super(source);
146 this.ds = ds;
147 }
148
149 /**
150 * Returns the affected dataset.
151 * @return the affected dataset, or null if the queue has been globally emptied
152 */
153 public DataSet getDataSet() {
154 return ds;
155 }
156
157 @Override
158 void fire(CommandQueuePreciseListener listener) {
159 listener.cleaned(this);
160 }
161 }
162
163 /**
164 * Event fired after a command has been undone.
165 * @since 13729
166 */
167 public static final class CommandUndoneEvent extends CommandQueueEvent {
168
169 private static final long serialVersionUID = 1L;
170 private final Command cmd;
171
172 private CommandUndoneEvent(UndoRedoHandler source, Command cmd) {
173 super(source);
174 this.cmd = Objects.requireNonNull(cmd);
175 }
176
177 /**
178 * Returns the undone command.
179 * @return the undone command
180 */
181 public Command getCommand() {
182 return cmd;
183 }
184
185 @Override
186 void fire(CommandQueuePreciseListener listener) {
187 listener.commandUndone(this);
188 }
189 }
190
191 /**
192 * Event fired after a command has been redone.
193 * @since 13729
194 */
195 public static final class CommandRedoneEvent extends CommandQueueEvent {
196
197 private static final long serialVersionUID = 1L;
198 private final Command cmd;
199
200 private CommandRedoneEvent(UndoRedoHandler source, Command cmd) {
201 super(source);
202 this.cmd = Objects.requireNonNull(cmd);
203 }
204
205 /**
206 * Returns the redone command.
207 * @return the redone command
208 */
209 public Command getCommand() {
210 return cmd;
211 }
212
213 @Override
214 void fire(CommandQueuePreciseListener listener) {
215 listener.commandRedone(this);
216 }
217 }
218
219 /**
220 * Gets the last command that was executed on the command stack.
221 * @return That command or <code>null</code> if there is no such command.
222 * @since #12316
223 */
224 public Command getLastCommand() {
225 return commands.peekLast();
226 }
227
228 /**
229 * Executes the command and add it to the intern command queue.
230 * @param c The command to execute. Must not be {@code null}.
231 */
232 public void addNoRedraw(final Command c) {
233 CheckParameterUtil.ensureParameterNotNull(c, "c");
234 c.executeCommand();
235 commands.add(c);
236 // Limit the number of commands in the undo list.
237 // Currently you have to undo the commands one by one. If
238 // this changes, a higher default value may be reasonable.
239 if (commands.size() > Config.getPref().getInt("undo.max", 1000)) {
240 commands.removeFirst();
241 }
242 redoCommands.clear();
243 }
244
245 /**
246 * Fires a commands change event after adding a command.
247 * @param cmd command added
248 */
249 public void afterAdd(Command cmd) {
250 if (cmd != null) {
251 fireEvent(new CommandAddedEvent(this, cmd));
252 }
253 fireCommandsChanged();
254 }
255
256 /**
257 * Executes the command and add it to the intern command queue.
258 * @param c The command to execute. Must not be {@code null}.
259 */
260 public synchronized void add(final Command c) {
261 addNoRedraw(c);
262 afterAdd(c);
263 }
264
265 /**
266 * Undoes the last added command.
267 */
268 public void undo() {
269 undo(1);
270 }
271
272 /**
273 * Undoes multiple commands.
274 * @param num The number of commands to undo
275 */
276 public synchronized void undo(int num) {
277 if (commands.isEmpty())
278 return;
279 DataSet ds = Main.main.getEditDataSet();
280 if (ds != null) {
281 ds.beginUpdate();
282 }
283 try {
284 for (int i = 1; i <= num; ++i) {
285 final Command c = commands.removeLast();
286 c.undoCommand();
287 redoCommands.addFirst(c);
288 fireEvent(new CommandUndoneEvent(this, c));
289 if (commands.isEmpty()) {
290 break;
291 }
292 }
293 } finally {
294 if (ds != null) {
295 ds.endUpdate();
296 }
297 }
298 fireCommandsChanged();
299 }
300
301 /**
302 * Redoes the last undoed command.
303 */
304 public void redo() {
305 redo(1);
306 }
307
308 /**
309 * Redoes multiple commands.
310 * @param num The number of commands to redo
311 */
312 public void redo(int num) {
313 if (redoCommands.isEmpty())
314 return;
315 for (int i = 0; i < num; ++i) {
316 final Command c = redoCommands.removeFirst();
317 c.executeCommand();
318 commands.add(c);
319 fireEvent(new CommandRedoneEvent(this, c));
320 if (redoCommands.isEmpty()) {
321 break;
322 }
323 }
324 fireCommandsChanged();
325 }
326
327 /**
328 * Fires a command change to all listeners.
329 */
330 private void fireCommandsChanged() {
331 for (final CommandQueueListener l : listenerCommands) {
332 l.commandChanged(commands.size(), redoCommands.size());
333 }
334 }
335
336 private void fireEvent(CommandQueueEvent e) {
337 preciseListenerCommands.forEach(e::fire);
338 }
339
340 /**
341 * Resets the undo/redo list.
342 */
343 public void clean() {
344 redoCommands.clear();
345 commands.clear();
346 fireEvent(new CommandQueueCleanedEvent(this, null));
347 fireCommandsChanged();
348 }
349
350 /**
351 * Resets all commands that affect the given dataset.
352 * @param dataSet The data set that was affected.
353 * @since 12718
354 */
355 public void clean(DataSet dataSet) {
356 if (dataSet == null)
357 return;
358 boolean changed = false;
359 for (Iterator<Command> it = commands.iterator(); it.hasNext();) {
360 if (it.next().getAffectedDataSet() == dataSet) {
361 it.remove();
362 changed = true;
363 }
364 }
365 for (Iterator<Command> it = redoCommands.iterator(); it.hasNext();) {
366 if (it.next().getAffectedDataSet() == dataSet) {
367 it.remove();
368 changed = true;
369 }
370 }
371 if (changed) {
372 fireEvent(new CommandQueueCleanedEvent(this, dataSet));
373 fireCommandsChanged();
374 }
375 }
376
377 /**
378 * Removes a command queue listener.
379 * @param l The command queue listener to remove
380 */
381 public void removeCommandQueueListener(CommandQueueListener l) {
382 listenerCommands.remove(l);
383 }
384
385 /**
386 * Adds a command queue listener.
387 * @param l The command queue listener to add
388 * @return {@code true} if the listener has been added, {@code false} otherwise
389 */
390 public boolean addCommandQueueListener(CommandQueueListener l) {
391 return listenerCommands.add(l);
392 }
393
394 /**
395 * Removes a precise command queue listener.
396 * @param l The precise command queue listener to remove
397 * @since 13729
398 */
399 public void removeCommandQueuePreciseListener(CommandQueuePreciseListener l) {
400 preciseListenerCommands.remove(l);
401 }
402
403 /**
404 * Adds a precise command queue listener.
405 * @param l The precise command queue listener to add
406 * @return {@code true} if the listener has been added, {@code false} otherwise
407 * @since 13729
408 */
409 public boolean addCommandQueuePreciseListener(CommandQueuePreciseListener l) {
410 return preciseListenerCommands.add(l);
411 }
412}
Note: See TracBrowser for help on using the repository browser.