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

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

see #10387 - refactor various actions and commands so they can be used without data layer

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