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

Last change on this file since 12846 was 12846, checked in by bastiK, 7 years ago

see #15229 - use Config.getPref() wherever possible

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