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