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    }