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

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

see #15182 - deprecate Main.getLayerManager(). Replacement: gui.MainApplication.getLayerManager()

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