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

Last change on this file since 14628 was 14381, checked in by Don-vip, 5 years ago

fix #16875 - command stack dialog was not updated after fixing errors from validator dialog

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