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

Last change on this file since 12739 was 12718, checked in by Don-vip, 7 years ago

see #13036 - see #15229 - see #15182 - make Commands depends only on a DataSet, not a Layer. This removes a lot of GUI dependencies

  • Property svn:eol-style set to native
File size: 7.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data;
3
4import java.util.Collection;
5import java.util.Iterator;
6import java.util.LinkedList;
7import java.util.Optional;
8
9import org.openstreetmap.josm.Main;
10import org.openstreetmap.josm.command.Command;
11import org.openstreetmap.josm.data.osm.DataSet;
12import org.openstreetmap.josm.data.osm.OsmPrimitive;
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
35 /**
36 * Constructs a new {@code UndoRedoHandler}.
37 */
38 public UndoRedoHandler() {
39 // Do nothing
40 }
41
42 /**
43 * A listener that gets notified of command queue (undo/redo) size changes.
44 * @since 12718 (moved from {@code OsmDataLayer}
45 */
46 @FunctionalInterface
47 public interface CommandQueueListener {
48 /**
49 * Notifies the listener about the new queue size
50 * @param queueSize Undo stack size
51 * @param redoSize Redo stack size
52 */
53 void commandChanged(int queueSize, int redoSize);
54 }
55
56 /**
57 * Gets the last command that was executed on the command stack.
58 * @return That command or <code>null</code> if there is no such command.
59 * @since #12316
60 */
61 public Command getLastCommand() {
62 return commands.peekLast();
63 }
64
65 /**
66 * Executes the command and add it to the intern command queue.
67 * @param c The command to execute. Must not be {@code null}.
68 */
69 public void addNoRedraw(final Command c) {
70 CheckParameterUtil.ensureParameterNotNull(c, "c");
71 c.executeCommand();
72 commands.add(c);
73 // Limit the number of commands in the undo list.
74 // Currently you have to undo the commands one by one. If
75 // this changes, a higher default value may be reasonable.
76 if (commands.size() > Main.pref.getInteger("undo.max", 1000)) {
77 commands.removeFirst();
78 }
79 redoCommands.clear();
80 }
81
82 /**
83 * Fires a commands change event after adding a command.
84 */
85 public void afterAdd() {
86 fireCommandsChanged();
87 }
88
89 /**
90 * Executes the command and add it to the intern command queue.
91 * @param c The command to execute. Must not be {@code null}.
92 */
93 public synchronized void add(final Command c) {
94 DataSet ds = Optional.ofNullable(c.getAffectedDataSet()).orElseGet(() -> Main.main.getEditDataSet());
95 Collection<? extends OsmPrimitive> oldSelection = null;
96 if (ds != null) {
97 oldSelection = ds.getSelected();
98 }
99 addNoRedraw(c);
100 afterAdd();
101
102 // the command may have changed the selection so tell the listeners about the current situation
103 if (ds != null) {
104 fireIfSelectionChanged(ds, oldSelection);
105 }
106 }
107
108 /**
109 * Undoes the last added command.
110 */
111 public void undo() {
112 undo(1);
113 }
114
115 /**
116 * Undoes multiple commands.
117 * @param num The number of commands to undo
118 */
119 public synchronized void undo(int num) {
120 if (commands.isEmpty())
121 return;
122 DataSet ds = Main.main.getEditDataSet();
123 Collection<? extends OsmPrimitive> oldSelection = null;
124 if (ds != null) {
125 oldSelection = ds.getSelected();
126 ds.beginUpdate();
127 }
128 try {
129 for (int i = 1; i <= num; ++i) {
130 final Command c = commands.removeLast();
131 c.undoCommand();
132 redoCommands.addFirst(c);
133 if (commands.isEmpty()) {
134 break;
135 }
136 }
137 } finally {
138 if (ds != null) {
139 ds.endUpdate();
140 }
141 }
142 fireCommandsChanged();
143 if (ds != null) {
144 fireIfSelectionChanged(ds, oldSelection);
145 }
146 }
147
148 /**
149 * Redoes the last undoed command.
150 */
151 public void redo() {
152 redo(1);
153 }
154
155 /**
156 * Redoes multiple commands.
157 * @param num The number of commands to redo
158 */
159 public void redo(int num) {
160 if (redoCommands.isEmpty())
161 return;
162 DataSet ds = Main.main.getEditDataSet();
163 Collection<? extends OsmPrimitive> oldSelection = ds.getSelected();
164 for (int i = 0; i < num; ++i) {
165 final Command c = redoCommands.removeFirst();
166 c.executeCommand();
167 commands.add(c);
168 if (redoCommands.isEmpty()) {
169 break;
170 }
171 }
172 fireCommandsChanged();
173 fireIfSelectionChanged(ds, oldSelection);
174 }
175
176 private static void fireIfSelectionChanged(DataSet ds, Collection<? extends OsmPrimitive> oldSelection) {
177 Collection<? extends OsmPrimitive> newSelection = ds.getSelected();
178 if (!oldSelection.equals(newSelection)) {
179 ds.fireSelectionChanged();
180 }
181 }
182
183 /**
184 * Fires a command change to all listeners.
185 */
186 private void fireCommandsChanged() {
187 for (final CommandQueueListener l : listenerCommands) {
188 l.commandChanged(commands.size(), redoCommands.size());
189 }
190 }
191
192 /**
193 * Resets the undo/redo list.
194 */
195 public void clean() {
196 redoCommands.clear();
197 commands.clear();
198 fireCommandsChanged();
199 }
200
201 /**
202 * Resets all commands that affect the given dataset.
203 * @param dataSet The data set that was affected.
204 * @since 12718
205 */
206 public void clean(DataSet dataSet) {
207 if (dataSet == null)
208 return;
209 boolean changed = false;
210 for (Iterator<Command> it = commands.iterator(); it.hasNext();) {
211 if (it.next().getAffectedDataSet() == dataSet) {
212 it.remove();
213 changed = true;
214 }
215 }
216 for (Iterator<Command> it = redoCommands.iterator(); it.hasNext();) {
217 if (it.next().getAffectedDataSet() == dataSet) {
218 it.remove();
219 changed = true;
220 }
221 }
222 if (changed) {
223 fireCommandsChanged();
224 }
225 }
226
227 /**
228 * Removes a command queue listener.
229 * @param l The command queue listener to remove
230 */
231 public void removeCommandQueueListener(CommandQueueListener l) {
232 listenerCommands.remove(l);
233 }
234
235 /**
236 * Adds a command queue listener.
237 * @param l The commands queue listener to add
238 * @return {@code true} if the listener has been added, {@code false} otherwise
239 */
240 public boolean addCommandQueueListener(CommandQueueListener l) {
241 return listenerCommands.add(l);
242 }
243}
Note: See TracBrowser for help on using the repository browser.