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 } finally { 099 Main.main.getCurrentDataSet().endUpdate(); 100 } 101 fireCommandsChanged(); 102 Collection<? extends OsmPrimitive> newSelection = Main.main.getCurrentDataSet().getSelected(); 103 if (!oldSelection.equals(newSelection)) { 104 Main.main.getCurrentDataSet().fireSelectionChanged(); 105 } 106 } 107 108 /** 109 * Redoes the last undoed command. 110 */ 111 public void redo() { 112 redo(1); 113 } 114 115 /** 116 * Redoes multiple commands. 117 * @param num The number of commands to redo 118 */ 119 public void redo(int num) { 120 if (redoCommands.isEmpty()) 121 return; 122 Collection<? extends OsmPrimitive> oldSelection = Main.main.getCurrentDataSet().getSelected(); 123 for (int i = 0; i < num; ++i) { 124 final Command c = redoCommands.removeFirst(); 125 c.executeCommand(); 126 commands.add(c); 127 if (redoCommands.isEmpty()) { 128 break; 129 } 130 } 131 fireCommandsChanged(); 132 Collection<? extends OsmPrimitive> newSelection = Main.main.getCurrentDataSet().getSelected(); 133 if (!oldSelection.equals(newSelection)) { 134 Main.main.getCurrentDataSet().fireSelectionChanged(); 135 } 136 } 137 138 public void fireCommandsChanged() { 139 for (final CommandQueueListener l : listenerCommands) { 140 l.commandChanged(commands.size(), redoCommands.size()); 141 } 142 } 143 144 public void clean() { 145 redoCommands.clear(); 146 commands.clear(); 147 fireCommandsChanged(); 148 } 149 150 public void clean(Layer layer) { 151 if (layer == null) 152 return; 153 boolean changed = false; 154 for (Iterator<Command> it = commands.iterator(); it.hasNext();) { 155 if (it.next().invalidBecauselayerRemoved(layer)) { 156 it.remove(); 157 changed = true; 158 } 159 } 160 for (Iterator<Command> it = redoCommands.iterator(); it.hasNext();) { 161 if (it.next().invalidBecauselayerRemoved(layer)) { 162 it.remove(); 163 changed = true; 164 } 165 } 166 if (changed) { 167 fireCommandsChanged(); 168 } 169 } 170 171 @Override 172 public void layerRemoved(Layer oldLayer) { 173 clean(oldLayer); 174 } 175 176 @Override 177 public void layerAdded(Layer newLayer) { 178 // Do nothing 179 } 180 181 @Override 182 public void activeLayerChange(Layer oldLayer, Layer newLayer) { 183 // Do nothing 184 } 185 186 /** 187 * Removes a command queue listener. 188 * @param l The command queue listener to remove 189 */ 190 public void removeCommandQueueListener(CommandQueueListener l) { 191 listenerCommands.remove(l); 192 } 193 194 /** 195 * Adds a command queue listener. 196 * @param l The commands queue listener to add 197 * @return {@code true} if the listener has been added, {@code false} otherwise 198 */ 199 public boolean addCommandQueueListener(CommandQueueListener l) { 200 return listenerCommands.add(l); 201 } 202}