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.MapView; 013import org.openstreetmap.josm.gui.layer.Layer; 014import org.openstreetmap.josm.gui.layer.OsmDataLayer.CommandQueueListener; 015import org.openstreetmap.josm.tools.CheckParameterUtil; 016 017public class UndoRedoHandler implements MapView.LayerChangeListener { 018 019 /** 020 * All commands that were made on the dataset. Don't write from outside! 021 */ 022 public final LinkedList<Command> commands = new LinkedList<>(); 023 /** 024 * The stack for redoing commands 025 */ 026 public final LinkedList<Command> redoCommands = new LinkedList<>(); 027 028 private final LinkedList<CommandQueueListener> listenerCommands = new LinkedList<>(); 029 030 /** 031 * Constructs a new {@code UndoRedoHandler}. 032 */ 033 public UndoRedoHandler() { 034 MapView.addLayerChangeListener(this); 035 } 036 037 /** 038 * Executes the command and add it to the intern command queue. 039 * @param c The command to execute. Must not be {@code null}. 040 */ 041 public void addNoRedraw(final Command c) { 042 CheckParameterUtil.ensureParameterNotNull(c, "c"); 043 c.executeCommand(); 044 commands.add(c); 045 // Limit the number of commands in the undo list. 046 // Currently you have to undo the commands one by one. If 047 // this changes, a higher default value may be reasonable. 048 if (commands.size() > Main.pref.getInteger("undo.max", 1000)) { 049 commands.removeFirst(); 050 } 051 redoCommands.clear(); 052 } 053 054 public void afterAdd() { 055 fireCommandsChanged(); 056 057 // the command may have changed the selection so tell the listeners about the current situation 058 DataSet ds = Main.main.getCurrentDataSet(); 059 if (ds != null) { 060 ds.fireSelectionChanged(); 061 } 062 } 063 064 /** 065 * Executes the command and add it to the intern command queue. 066 * @param c The command to execute. Must not be {@code null}. 067 */ 068 public synchronized void add(final Command c) { 069 addNoRedraw(c); 070 afterAdd(); 071 } 072 073 /** 074 * Undoes the last added command. 075 */ 076 public void undo() { 077 undo(1); 078 } 079 080 /** 081 * Undoes multiple commands. 082 * @param num The number of commands to undo 083 */ 084 public synchronized void undo(int num) { 085 if (commands.isEmpty()) 086 return; 087 Collection<? extends OsmPrimitive> oldSelection = Main.main.getCurrentDataSet().getSelected(); 088 Main.main.getCurrentDataSet().beginUpdate(); 089 try { 090 for (int i=1; i<=num; ++i) { 091 final Command c = commands.removeLast(); 092 c.undoCommand(); 093 redoCommands.addFirst(c); 094 if (commands.isEmpty()) { 095 break; 096 } 097 } 098 } 099 finally { 100 Main.main.getCurrentDataSet().endUpdate(); 101 } 102 fireCommandsChanged(); 103 Collection<? extends OsmPrimitive> newSelection = Main.main.getCurrentDataSet().getSelected(); 104 if (!oldSelection.equals(newSelection)) { 105 Main.main.getCurrentDataSet().fireSelectionChanged(); 106 } 107 } 108 109 /** 110 * Redoes the last undoed command. 111 */ 112 public void redo() { 113 redo(1); 114 } 115 116 /** 117 * Redoes multiple commands. 118 * @param num The number of commands to redo 119 */ 120 public void redo(int num) { 121 if (redoCommands.isEmpty()) 122 return; 123 Collection<? extends OsmPrimitive> oldSelection = Main.main.getCurrentDataSet().getSelected(); 124 for (int i=0; i<num; ++i) { 125 final Command c = redoCommands.removeFirst(); 126 c.executeCommand(); 127 commands.add(c); 128 if (redoCommands.isEmpty()) { 129 break; 130 } 131 } 132 fireCommandsChanged(); 133 Collection<? extends OsmPrimitive> newSelection = Main.main.getCurrentDataSet().getSelected(); 134 if (!oldSelection.equals(newSelection)) { 135 Main.main.getCurrentDataSet().fireSelectionChanged(); 136 } 137 } 138 139 public void fireCommandsChanged() { 140 for (final CommandQueueListener l : listenerCommands) { 141 l.commandChanged(commands.size(), redoCommands.size()); 142 } 143 } 144 145 public void clean() { 146 redoCommands.clear(); 147 commands.clear(); 148 fireCommandsChanged(); 149 } 150 151 public void clean(Layer layer) { 152 if (layer == null) 153 return; 154 boolean changed = false; 155 for (Iterator<Command> it = commands.iterator(); it.hasNext();) { 156 if (it.next().invalidBecauselayerRemoved(layer)) { 157 it.remove(); 158 changed = true; 159 } 160 } 161 for (Iterator<Command> it = redoCommands.iterator(); it.hasNext();) { 162 if (it.next().invalidBecauselayerRemoved(layer)) { 163 it.remove(); 164 changed = true; 165 } 166 } 167 if (changed) { 168 fireCommandsChanged(); 169 } 170 } 171 172 @Override 173 public void layerRemoved(Layer oldLayer) { 174 clean(oldLayer); 175 } 176 177 @Override 178 public void layerAdded(Layer newLayer) {} 179 @Override 180 public void activeLayerChange(Layer oldLayer, Layer newLayer) {} 181 182 /** 183 * Removes a command queue listener. 184 * @param l The command queue listener to remove 185 */ 186 public void removeCommandQueueListener(CommandQueueListener l) { 187 listenerCommands.remove(l); 188 } 189 190 /** 191 * Adds a command queue listener. 192 * @param l The commands queue listener to add 193 * @return {@code true} if the listener has been added, {@code false} otherwise 194 */ 195 public boolean addCommandQueueListener(CommandQueueListener l) { 196 return listenerCommands.add(l); 197 } 198}