source: josm/trunk/src/org/openstreetmap/josm/data/UndoRedoHandler.java @ 12841

Last change on this file since 12841 was 12841, checked in by bastiK, 3 months ago

see #15229 - fix deprecations caused by [12840]

  • Property svn:eol-style set to native
File size: 7.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data;
3
4import java.util.Collection;
5import java.util.Iterator;
6import java.util.LinkedList;
7import java.util.Optional;
8
9import org.openstreetmap.josm.Main;
10import org.openstreetmap.josm.command.Command;
11import org.openstreetmap.josm.data.osm.DataSet;
12import org.openstreetmap.josm.data.osm.OsmPrimitive;
13import org.openstreetmap.josm.tools.CheckParameterUtil;
14
15/**
16 * This is the global undo/redo handler for all {@link DataSet}s.
17 * <p>
18 * If you want to change a data set, you can use {@link #add(Command)} to execute a command on it and make that command undoable.
19 */
20public class UndoRedoHandler {
21
22    /**
23     * All commands that were made on the dataset. Don't write from outside!
24     *
25     * @see #getLastCommand()
26     */
27    public final LinkedList<Command> commands = new LinkedList<>();
28    /**
29     * The stack for redoing commands
30     */
31    public final LinkedList<Command> redoCommands = new LinkedList<>();
32
33    private final LinkedList<CommandQueueListener> listenerCommands = new LinkedList<>();
34
35    /**
36     * Constructs a new {@code UndoRedoHandler}.
37     */
38    public UndoRedoHandler() {
39        // Do nothing
40    }
41
42    /**
43     * A listener that gets notified of command queue (undo/redo) size changes.
44     * @since 12718 (moved from {@code OsmDataLayer}
45     */
46    @FunctionalInterface
47    public interface CommandQueueListener {
48        /**
49         * Notifies the listener about the new queue size
50         * @param queueSize Undo stack size
51         * @param redoSize Redo stack size
52         */
53        void commandChanged(int queueSize, int redoSize);
54    }
55
56    /**
57     * Gets the last command that was executed on the command stack.
58     * @return That command or <code>null</code> if there is no such command.
59     * @since #12316
60     */
61    public Command getLastCommand() {
62        return commands.peekLast();
63    }
64
65    /**
66     * Executes the command and add it to the intern command queue.
67     * @param c The command to execute. Must not be {@code null}.
68     */
69    public void addNoRedraw(final Command c) {
70        CheckParameterUtil.ensureParameterNotNull(c, "c");
71        c.executeCommand();
72        commands.add(c);
73        // Limit the number of commands in the undo list.
74        // Currently you have to undo the commands one by one. If
75        // this changes, a higher default value may be reasonable.
76        if (commands.size() > Main.pref.getInt("undo.max", 1000)) {
77            commands.removeFirst();
78        }
79        redoCommands.clear();
80    }
81
82    /**
83     * Fires a commands change event after adding a command.
84     */
85    public void afterAdd() {
86        fireCommandsChanged();
87    }
88
89    /**
90     * Executes the command and add it to the intern command queue.
91     * @param c The command to execute. Must not be {@code null}.
92     */
93    public synchronized void add(final Command c) {
94        DataSet ds = Optional.ofNullable(c.getAffectedDataSet()).orElseGet(() -> Main.main.getEditDataSet());
95        Collection<? extends OsmPrimitive> oldSelection = null;
96        if (ds != null) {
97            oldSelection = ds.getSelected();
98        }
99        addNoRedraw(c);
100        afterAdd();
101
102        // the command may have changed the selection so tell the listeners about the current situation
103        if (ds != null) {
104            fireIfSelectionChanged(ds, oldSelection);
105        }
106    }
107
108    /**
109     * Undoes the last added command.
110     */
111    public void undo() {
112        undo(1);
113    }
114
115    /**
116     * Undoes multiple commands.
117     * @param num The number of commands to undo
118     */
119    public synchronized void undo(int num) {
120        if (commands.isEmpty())
121            return;
122        DataSet ds = Main.main.getEditDataSet();
123        Collection<? extends OsmPrimitive> oldSelection = null;
124        if (ds != null) {
125            oldSelection = ds.getSelected();
126            ds.beginUpdate();
127        }
128        try {
129            for (int i = 1; i <= num; ++i) {
130                final Command c = commands.removeLast();
131                c.undoCommand();
132                redoCommands.addFirst(c);
133                if (commands.isEmpty()) {
134                    break;
135                }
136            }
137        } finally {
138            if (ds != null) {
139                ds.endUpdate();
140            }
141        }
142        fireCommandsChanged();
143        if (ds != null) {
144            fireIfSelectionChanged(ds, oldSelection);
145        }
146    }
147
148    /**
149     * Redoes the last undoed command.
150     */
151    public void redo() {
152        redo(1);
153    }
154
155    /**
156     * Redoes multiple commands.
157     * @param num The number of commands to redo
158     */
159    public void redo(int num) {
160        if (redoCommands.isEmpty())
161            return;
162        DataSet ds = Main.main.getEditDataSet();
163        Collection<? extends OsmPrimitive> oldSelection = ds.getSelected();
164        for (int i = 0; i < num; ++i) {
165            final Command c = redoCommands.removeFirst();
166            c.executeCommand();
167            commands.add(c);
168            if (redoCommands.isEmpty()) {
169                break;
170            }
171        }
172        fireCommandsChanged();
173        fireIfSelectionChanged(ds, oldSelection);
174    }
175
176    private static void fireIfSelectionChanged(DataSet ds, Collection<? extends OsmPrimitive> oldSelection) {
177        Collection<? extends OsmPrimitive> newSelection = ds.getSelected();
178        if (!oldSelection.equals(newSelection)) {
179            ds.fireSelectionChanged();
180        }
181    }
182
183    /**
184     * Fires a command change to all listeners.
185     */
186    private void fireCommandsChanged() {
187        for (final CommandQueueListener l : listenerCommands) {
188            l.commandChanged(commands.size(), redoCommands.size());
189        }
190    }
191
192    /**
193     * Resets the undo/redo list.
194     */
195    public void clean() {
196        redoCommands.clear();
197        commands.clear();
198        fireCommandsChanged();
199    }
200
201    /**
202     * Resets all commands that affect the given dataset.
203     * @param dataSet The data set that was affected.
204     * @since 12718
205     */
206    public void clean(DataSet dataSet) {
207        if (dataSet == null)
208            return;
209        boolean changed = false;
210        for (Iterator<Command> it = commands.iterator(); it.hasNext();) {
211            if (it.next().getAffectedDataSet() == dataSet) {
212                it.remove();
213                changed = true;
214            }
215        }
216        for (Iterator<Command> it = redoCommands.iterator(); it.hasNext();) {
217            if (it.next().getAffectedDataSet() == dataSet) {
218                it.remove();
219                changed = true;
220            }
221        }
222        if (changed) {
223            fireCommandsChanged();
224        }
225    }
226
227    /**
228     * Removes a command queue listener.
229     * @param l The command queue listener to remove
230     */
231    public void removeCommandQueueListener(CommandQueueListener l) {
232        listenerCommands.remove(l);
233    }
234
235    /**
236     * Adds a command queue listener.
237     * @param l The commands queue listener to add
238     * @return {@code true} if the listener has been added, {@code false} otherwise
239     */
240    public boolean addCommandQueueListener(CommandQueueListener l) {
241        return listenerCommands.add(l);
242    }
243}
Note: See TracBrowser for help on using the repository browser.