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