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

Last change on this file since 12635 was 12316, checked in by michael2402, 7 years ago

Add a method to undo/redo to get the last command.

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