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

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

see #15229 - deprecate Main.main - new class OsmDataManager

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