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

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

add OsmData interface, abstraction of DataSet

  • 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 * @since 13729
249 */
250 public void afterAdd(Command cmd) {
251 if (cmd != null) {
252 fireEvent(new CommandAddedEvent(this, cmd));
253 }
254 fireCommandsChanged();
255 }
256
257 /**
258 * Executes the command and add it to the intern command queue.
259 * @param c The command to execute. Must not be {@code null}.
260 */
261 public synchronized void add(final Command c) {
262 addNoRedraw(c);
263 afterAdd(c);
264 }
265
266 /**
267 * Undoes the last added command.
268 */
269 public void undo() {
270 undo(1);
271 }
272
273 /**
274 * Undoes multiple commands.
275 * @param num The number of commands to undo
276 */
277 public synchronized void undo(int num) {
278 if (commands.isEmpty())
279 return;
280 DataSet ds = Main.main.getEditDataSet();
281 if (ds != null) {
282 ds.beginUpdate();
283 }
284 try {
285 for (int i = 1; i <= num; ++i) {
286 final Command c = commands.removeLast();
287 c.undoCommand();
288 redoCommands.addFirst(c);
289 fireEvent(new CommandUndoneEvent(this, c));
290 if (commands.isEmpty()) {
291 break;
292 }
293 }
294 } finally {
295 if (ds != null) {
296 ds.endUpdate();
297 }
298 }
299 fireCommandsChanged();
300 }
301
302 /**
303 * Redoes the last undoed command.
304 */
305 public void redo() {
306 redo(1);
307 }
308
309 /**
310 * Redoes multiple commands.
311 * @param num The number of commands to redo
312 */
313 public void redo(int num) {
314 if (redoCommands.isEmpty())
315 return;
316 for (int i = 0; i < num; ++i) {
317 final Command c = redoCommands.removeFirst();
318 c.executeCommand();
319 commands.add(c);
320 fireEvent(new CommandRedoneEvent(this, c));
321 if (redoCommands.isEmpty()) {
322 break;
323 }
324 }
325 fireCommandsChanged();
326 }
327
328 /**
329 * Fires a command change to all listeners.
330 */
331 private void fireCommandsChanged() {
332 for (final CommandQueueListener l : listenerCommands) {
333 l.commandChanged(commands.size(), redoCommands.size());
334 }
335 }
336
337 private void fireEvent(CommandQueueEvent e) {
338 preciseListenerCommands.forEach(e::fire);
339 }
340
341 /**
342 * Resets the undo/redo list.
343 */
344 public void clean() {
345 redoCommands.clear();
346 commands.clear();
347 fireEvent(new CommandQueueCleanedEvent(this, null));
348 fireCommandsChanged();
349 }
350
351 /**
352 * Resets all commands that affect the given dataset.
353 * @param dataSet The data set that was affected.
354 * @since 12718
355 */
356 public void clean(DataSet dataSet) {
357 if (dataSet == null)
358 return;
359 boolean changed = false;
360 for (Iterator<Command> it = commands.iterator(); it.hasNext();) {
361 if (it.next().getAffectedDataSet() == dataSet) {
362 it.remove();
363 changed = true;
364 }
365 }
366 for (Iterator<Command> it = redoCommands.iterator(); it.hasNext();) {
367 if (it.next().getAffectedDataSet() == dataSet) {
368 it.remove();
369 changed = true;
370 }
371 }
372 if (changed) {
373 fireEvent(new CommandQueueCleanedEvent(this, dataSet));
374 fireCommandsChanged();
375 }
376 }
377
378 /**
379 * Removes a command queue listener.
380 * @param l The command queue listener to remove
381 */
382 public void removeCommandQueueListener(CommandQueueListener l) {
383 listenerCommands.remove(l);
384 }
385
386 /**
387 * Adds a command queue listener.
388 * @param l The command queue listener to add
389 * @return {@code true} if the listener has been added, {@code false} otherwise
390 */
391 public boolean addCommandQueueListener(CommandQueueListener l) {
392 return listenerCommands.add(l);
393 }
394
395 /**
396 * Removes a precise command queue listener.
397 * @param l The precise command queue listener to remove
398 * @since 13729
399 */
400 public void removeCommandQueuePreciseListener(CommandQueuePreciseListener l) {
401 preciseListenerCommands.remove(l);
402 }
403
404 /**
405 * Adds a precise command queue listener.
406 * @param l The precise command queue listener to add
407 * @return {@code true} if the listener has been added, {@code false} otherwise
408 * @since 13729
409 */
410 public boolean addCommandQueuePreciseListener(CommandQueuePreciseListener l) {
411 return preciseListenerCommands.add(l);
412 }
413}
Note: See TracBrowser for help on using the repository browser.