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

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

fix #3346 - improve drastically the performance of fixing duplicate nodes by:

  • caching data sources area computation
  • moving layer invalidation from UndoRedoHandler.addNoRedraw to UndoRedoHandler.add
  • avoiding any EDT call when building tag conflict dialog if it's not meant to be displayed
  • Property svn:eol-style set to native
File size: 7.3 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 public final LinkedList<Command> commands = new LinkedList<>();
33 /**
34 * The stack for redoing commands
35 */
36 public final LinkedList<Command> redoCommands = new LinkedList<>();
37
38 private final LinkedList<CommandQueueListener> listenerCommands = new LinkedList<>();
39
40 /**
41 * Constructs a new {@code UndoRedoHandler}.
42 */
43 public UndoRedoHandler() {
44 Main.getLayerManager().addLayerChangeListener(this);
45 }
46
47 /**
48 * Executes the command and add it to the intern command queue.
49 * @param c The command to execute. Must not be {@code null}.
50 */
51 public void addNoRedraw(final Command c) {
52 CheckParameterUtil.ensureParameterNotNull(c, "c");
53 c.executeCommand();
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 = Optional.ofNullable(c.getAffectedDataSet()).orElseGet(() -> Main.getLayerManager().getEditDataSet());
77 Collection<? extends OsmPrimitive> oldSelection = null;
78 if (ds != null) {
79 oldSelection = ds.getSelected();
80 }
81 addNoRedraw(c);
82 c.invalidateAffectedLayers();
83 afterAdd();
84
85 // the command may have changed the selection so tell the listeners about the current situation
86 if (ds != null) {
87 fireIfSelectionChanged(ds, oldSelection);
88 }
89 }
90
91 /**
92 * Undoes the last added command.
93 */
94 public void undo() {
95 undo(1);
96 }
97
98 /**
99 * Undoes multiple commands.
100 * @param num The number of commands to undo
101 */
102 public synchronized void undo(int num) {
103 if (commands.isEmpty())
104 return;
105 DataSet ds = Main.getLayerManager().getEditDataSet();
106 Collection<? extends OsmPrimitive> oldSelection = null;
107 if (ds != null) {
108 oldSelection = ds.getSelected();
109 ds.beginUpdate();
110 }
111 try {
112 for (int i = 1; i <= num; ++i) {
113 final Command c = commands.removeLast();
114 c.undoCommand();
115 c.invalidateAffectedLayers();
116 redoCommands.addFirst(c);
117 if (commands.isEmpty()) {
118 break;
119 }
120 }
121 } finally {
122 if (ds != null) {
123 ds.endUpdate();
124 }
125 }
126 fireCommandsChanged();
127 if (ds != null) {
128 fireIfSelectionChanged(ds, oldSelection);
129 }
130 }
131
132 /**
133 * Redoes the last undoed command.
134 */
135 public void redo() {
136 redo(1);
137 }
138
139 /**
140 * Redoes multiple commands.
141 * @param num The number of commands to redo
142 */
143 public void redo(int num) {
144 if (redoCommands.isEmpty())
145 return;
146 DataSet ds = Main.getLayerManager().getEditDataSet();
147 Collection<? extends OsmPrimitive> oldSelection = ds.getSelected();
148 for (int i = 0; i < num; ++i) {
149 final Command c = redoCommands.removeFirst();
150 c.executeCommand();
151 c.invalidateAffectedLayers();
152 commands.add(c);
153 if (redoCommands.isEmpty()) {
154 break;
155 }
156 }
157 fireCommandsChanged();
158 fireIfSelectionChanged(ds, oldSelection);
159 }
160
161 private static void fireIfSelectionChanged(DataSet ds, Collection<? extends OsmPrimitive> oldSelection) {
162 Collection<? extends OsmPrimitive> newSelection = ds.getSelected();
163 if (!oldSelection.equals(newSelection)) {
164 ds.fireSelectionChanged();
165 }
166 }
167
168 /**
169 * Fires a command change to all listeners.
170 */
171 private void fireCommandsChanged() {
172 for (final CommandQueueListener l : listenerCommands) {
173 l.commandChanged(commands.size(), redoCommands.size());
174 }
175 }
176
177 /**
178 * Resets the undo/redo list.
179 */
180 public void clean() {
181 redoCommands.clear();
182 commands.clear();
183 fireCommandsChanged();
184 }
185
186 /**
187 * Resets all commands that affect the given layer.
188 * @param layer The layer that was affected.
189 */
190 public void clean(Layer layer) {
191 if (layer == null)
192 return;
193 boolean changed = false;
194 for (Iterator<Command> it = commands.iterator(); it.hasNext();) {
195 if (it.next().invalidBecauselayerRemoved(layer)) {
196 it.remove();
197 changed = true;
198 }
199 }
200 for (Iterator<Command> it = redoCommands.iterator(); it.hasNext();) {
201 if (it.next().invalidBecauselayerRemoved(layer)) {
202 it.remove();
203 changed = true;
204 }
205 }
206 if (changed) {
207 fireCommandsChanged();
208 }
209 }
210
211 @Override
212 public void layerRemoving(LayerRemoveEvent e) {
213 clean(e.getRemovedLayer());
214 }
215
216 @Override
217 public void layerAdded(LayerAddEvent e) {
218 // Do nothing
219 }
220
221 @Override
222 public void layerOrderChanged(LayerOrderChangeEvent e) {
223 // Do nothing
224 }
225
226 /**
227 * Removes a command queue listener.
228 * @param l The command queue listener to remove
229 */
230 public void removeCommandQueueListener(CommandQueueListener l) {
231 listenerCommands.remove(l);
232 }
233
234 /**
235 * Adds a command queue listener.
236 * @param l The commands queue listener to add
237 * @return {@code true} if the listener has been added, {@code false} otherwise
238 */
239 public boolean addCommandQueueListener(CommandQueueListener l) {
240 return listenerCommands.add(l);
241 }
242}
Note: See TracBrowser for help on using the repository browser.