001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data; 003 004import java.util.Collection; 005import java.util.Iterator; 006import java.util.LinkedList; 007 008import org.openstreetmap.josm.Main; 009import org.openstreetmap.josm.command.Command; 010import org.openstreetmap.josm.data.osm.DataSet; 011import org.openstreetmap.josm.data.osm.OsmPrimitive; 012import org.openstreetmap.josm.gui.layer.Layer; 013import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent; 014import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener; 015import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent; 016import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent; 017import org.openstreetmap.josm.gui.layer.OsmDataLayer; 018import org.openstreetmap.josm.gui.layer.OsmDataLayer.CommandQueueListener; 019import org.openstreetmap.josm.tools.CheckParameterUtil; 020 021/** 022 * This is the global undo/redo handler for all {@link OsmDataLayer}s. 023 * <p> 024 * 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. 025 */ 026public class UndoRedoHandler implements LayerChangeListener { 027 028 /** 029 * All commands that were made on the dataset. Don't write from outside! 030 */ 031 public final LinkedList<Command> commands = new LinkedList<>(); 032 /** 033 * The stack for redoing commands 034 */ 035 public final LinkedList<Command> redoCommands = new LinkedList<>(); 036 037 private final LinkedList<CommandQueueListener> listenerCommands = new LinkedList<>(); 038 039 /** 040 * Constructs a new {@code UndoRedoHandler}. 041 */ 042 public UndoRedoHandler() { 043 Main.getLayerManager().addLayerChangeListener(this); 044 } 045 046 /** 047 * Executes the command and add it to the intern command queue. 048 * @param c The command to execute. Must not be {@code null}. 049 */ 050 public void addNoRedraw(final Command c) { 051 CheckParameterUtil.ensureParameterNotNull(c, "c"); 052 c.executeCommand(); 053 c.invalidateAffectedLayers(); 054 commands.add(c); 055 // Limit the number of commands in the undo list. 056 // Currently you have to undo the commands one by one. If 057 // this changes, a higher default value may be reasonable. 058 if (commands.size() > Main.pref.getInteger("undo.max", 1000)) { 059 commands.removeFirst(); 060 } 061 redoCommands.clear(); 062 } 063 064 /** 065 * Fires a commands change event after adding a command. 066 */ 067 public void afterAdd() { 068 fireCommandsChanged(); 069 } 070 071 /** 072 * Executes the command and add it to the intern command queue. 073 * @param c The command to execute. Must not be {@code null}. 074 */ 075 public synchronized void add(final Command c) { 076 DataSet ds = c.getAffectedDataSet(); 077 if (ds == null) { 078 // old, legacy behaviour 079 ds = Main.getLayerManager().getEditDataSet(); 080 } 081 Collection<? extends OsmPrimitive> oldSelection = null; 082 if (ds != null) { 083 oldSelection = ds.getSelected(); 084 } 085 addNoRedraw(c); 086 afterAdd(); 087 088 // the command may have changed the selection so tell the listeners about the current situation 089 if (ds != null) { 090 fireIfSelectionChanged(ds, oldSelection); 091 } 092 } 093 094 /** 095 * Undoes the last added command. 096 */ 097 public void undo() { 098 undo(1); 099 } 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 = ds.getSelected(); 110 ds.beginUpdate(); 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 ds.endUpdate(); 123 } 124 fireCommandsChanged(); 125 fireIfSelectionChanged(ds, oldSelection); 126 } 127 128 /** 129 * Redoes the last undoed command. 130 */ 131 public void redo() { 132 redo(1); 133 } 134 135 /** 136 * Redoes multiple commands. 137 * @param num The number of commands to redo 138 */ 139 public void redo(int num) { 140 if (redoCommands.isEmpty()) 141 return; 142 DataSet ds = Main.getLayerManager().getEditDataSet(); 143 Collection<? extends OsmPrimitive> oldSelection = ds.getSelected(); 144 for (int i = 0; i < num; ++i) { 145 final Command c = redoCommands.removeFirst(); 146 c.executeCommand(); 147 c.invalidateAffectedLayers(); 148 commands.add(c); 149 if (redoCommands.isEmpty()) { 150 break; 151 } 152 } 153 fireCommandsChanged(); 154 fireIfSelectionChanged(ds, oldSelection); 155 } 156 157 private static void fireIfSelectionChanged(DataSet ds, Collection<? extends OsmPrimitive> oldSelection) { 158 Collection<? extends OsmPrimitive> newSelection = ds.getSelected(); 159 if (!oldSelection.equals(newSelection)) { 160 ds.fireSelectionChanged(); 161 } 162 } 163 164 /** 165 * Fires a command change to all listeners. 166 */ 167 private void fireCommandsChanged() { 168 for (final CommandQueueListener l : listenerCommands) { 169 l.commandChanged(commands.size(), redoCommands.size()); 170 } 171 } 172 173 /** 174 * Resets the undo/redo list. 175 */ 176 public void clean() { 177 redoCommands.clear(); 178 commands.clear(); 179 fireCommandsChanged(); 180 } 181 182 /** 183 * Resets all commands that affect the given layer. 184 * @param layer The layer that was affected. 185 */ 186 public void clean(Layer layer) { 187 if (layer == null) 188 return; 189 boolean changed = false; 190 for (Iterator<Command> it = commands.iterator(); it.hasNext();) { 191 if (it.next().invalidBecauselayerRemoved(layer)) { 192 it.remove(); 193 changed = true; 194 } 195 } 196 for (Iterator<Command> it = redoCommands.iterator(); it.hasNext();) { 197 if (it.next().invalidBecauselayerRemoved(layer)) { 198 it.remove(); 199 changed = true; 200 } 201 } 202 if (changed) { 203 fireCommandsChanged(); 204 } 205 } 206 207 @Override 208 public void layerRemoving(LayerRemoveEvent e) { 209 clean(e.getRemovedLayer()); 210 } 211 212 @Override 213 public void layerAdded(LayerAddEvent e) { 214 // Do nothing 215 } 216 217 @Override 218 public void layerOrderChanged(LayerOrderChangeEvent e) { 219 // Do nothing 220 } 221 222 /** 223 * Removes a command queue listener. 224 * @param l The command queue listener to remove 225 */ 226 public void removeCommandQueueListener(CommandQueueListener l) { 227 listenerCommands.remove(l); 228 } 229 230 /** 231 * Adds a command queue listener. 232 * @param l The commands queue listener to add 233 * @return {@code true} if the listener has been added, {@code false} otherwise 234 */ 235 public boolean addCommandQueueListener(CommandQueueListener l) { 236 return listenerCommands.add(l); 237 } 238}