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

Last change on this file since 12718 was 12718, checked in by Don-vip, 7 years ago

see #13036 - see #15229 - see #15182 - make Commands depends only on a DataSet, not a Layer. This removes a lot of GUI dependencies

  • Property svn:eol-style set to native
File size: 7.1 KB
RevLine 
[8378]1// License: GPL. For details, see LICENSE file.
[301]2package org.openstreetmap.josm.data;
3
[2539]4import java.util.Collection;
[304]5import java.util.Iterator;
[301]6import java.util.LinkedList;
[11553]7import java.util.Optional;
[301]8
9import org.openstreetmap.josm.Main;
10import org.openstreetmap.josm.command.Command;
[6994]11import org.openstreetmap.josm.data.osm.DataSet;
[2539]12import org.openstreetmap.josm.data.osm.OsmPrimitive;
[6413]13import org.openstreetmap.josm.tools.CheckParameterUtil;
[301]14
[10452]15/**
[12718]16 * This is the global undo/redo handler for all {@link DataSet}s.
[10452]17 * <p>
[12718]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.
[10452]19 */
[12718]20public class UndoRedoHandler {
[301]21
[1169]22 /**
23 * All commands that were made on the dataset. Don't write from outside!
[12316]24 *
25 * @see #getLastCommand()
[1169]26 */
[7005]27 public final LinkedList<Command> commands = new LinkedList<>();
[1169]28 /**
29 * The stack for redoing commands
30 */
[7005]31 public final LinkedList<Command> redoCommands = new LinkedList<>();
[301]32
[7005]33 private final LinkedList<CommandQueueListener> listenerCommands = new LinkedList<>();
[301]34
[6413]35 /**
36 * Constructs a new {@code UndoRedoHandler}.
37 */
[1169]38 public UndoRedoHandler() {
[12718]39 // Do nothing
[1169]40 }
[304]41
[1169]42 /**
[12718]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 /**
[12316]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 /**
[6413]66 * Executes the command and add it to the intern command queue.
67 * @param c The command to execute. Must not be {@code null}.
[1169]68 */
[2098]69 public void addNoRedraw(final Command c) {
[6413]70 CheckParameterUtil.ensureParameterNotNull(c, "c");
[1169]71 c.executeCommand();
72 commands.add(c);
[3143]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.getInteger("undo.max", 1000)) {
77 commands.removeFirst();
78 }
[1169]79 redoCommands.clear();
[2098]80 }
81
[10452]82 /**
83 * Fires a commands change event after adding a command.
84 */
[2098]85 public void afterAdd() {
[1169]86 fireCommandsChanged();
87 }
[304]88
[1169]89 /**
[6413]90 * Executes the command and add it to the intern command queue.
91 * @param c The command to execute. Must not be {@code null}.
[2098]92 */
[6986]93 public synchronized void add(final Command c) {
[12691]94 DataSet ds = Optional.ofNullable(c.getAffectedDataSet()).orElseGet(() -> Main.main.getEditDataSet());
[10467]95 Collection<? extends OsmPrimitive> oldSelection = null;
96 if (ds != null) {
97 oldSelection = ds.getSelected();
98 }
[2098]99 addNoRedraw(c);
100 afterAdd();
[10452]101
102 // the command may have changed the selection so tell the listeners about the current situation
[10467]103 if (ds != null) {
104 fireIfSelectionChanged(ds, oldSelection);
105 }
[2098]106 }
107
108 /**
[1169]109 * Undoes the last added command.
110 */
[3262]111 public void undo() {
112 undo(1);
113 }
114
115 /**
116 * Undoes multiple commands.
[6413]117 * @param num The number of commands to undo
[3262]118 */
[6986]119 public synchronized void undo(int num) {
[1169]120 if (commands.isEmpty())
121 return;
[12691]122 DataSet ds = Main.main.getEditDataSet();
[11240]123 Collection<? extends OsmPrimitive> oldSelection = null;
124 if (ds != null) {
125 oldSelection = ds.getSelected();
126 ds.beginUpdate();
127 }
[3910]128 try {
[8510]129 for (int i = 1; i <= num; ++i) {
[3910]130 final Command c = commands.removeLast();
131 c.undoCommand();
132 redoCommands.addFirst(c);
133 if (commands.isEmpty()) {
134 break;
135 }
[3479]136 }
[8342]137 } finally {
[11240]138 if (ds != null) {
139 ds.endUpdate();
140 }
[3910]141 }
[1169]142 fireCommandsChanged();
[11240]143 if (ds != null) {
144 fireIfSelectionChanged(ds, oldSelection);
145 }
[1169]146 }
[301]147
[1169]148 /**
149 * Redoes the last undoed command.
150 */
151 public void redo() {
[3262]152 redo(1);
153 }
154
155 /**
156 * Redoes multiple commands.
[6413]157 * @param num The number of commands to redo
[3262]158 */
159 public void redo(int num) {
[1169]160 if (redoCommands.isEmpty())
161 return;
[12691]162 DataSet ds = Main.main.getEditDataSet();
[10452]163 Collection<? extends OsmPrimitive> oldSelection = ds.getSelected();
[8510]164 for (int i = 0; i < num; ++i) {
[3275]165 final Command c = redoCommands.removeFirst();
[3262]166 c.executeCommand();
167 commands.add(c);
[3479]168 if (redoCommands.isEmpty()) {
[3262]169 break;
[3479]170 }
[3262]171 }
[1169]172 fireCommandsChanged();
[10452]173 fireIfSelectionChanged(ds, oldSelection);
174 }
175
176 private static void fireIfSelectionChanged(DataSet ds, Collection<? extends OsmPrimitive> oldSelection) {
177 Collection<? extends OsmPrimitive> newSelection = ds.getSelected();
[2552]178 if (!oldSelection.equals(newSelection)) {
[10452]179 ds.fireSelectionChanged();
[2552]180 }
[1169]181 }
[301]182
[10452]183 /**
184 * Fires a command change to all listeners.
185 */
186 private void fireCommandsChanged() {
[1814]187 for (final CommandQueueListener l : listenerCommands) {
[1169]188 l.commandChanged(commands.size(), redoCommands.size());
[1814]189 }
[1169]190 }
[301]191
[10452]192 /**
193 * Resets the undo/redo list.
194 */
[1169]195 public void clean() {
196 redoCommands.clear();
197 commands.clear();
198 fireCommandsChanged();
199 }
[304]200
[10452]201 /**
[12718]202 * Resets all commands that affect the given dataset.
203 * @param dataSet The data set that was affected.
204 * @since 12718
[10452]205 */
[12718]206 public void clean(DataSet dataSet) {
207 if (dataSet == null)
[1894]208 return;
[1169]209 boolean changed = false;
210 for (Iterator<Command> it = commands.iterator(); it.hasNext();) {
[12718]211 if (it.next().getAffectedDataSet() == dataSet) {
[1169]212 it.remove();
213 changed = true;
214 }
215 }
216 for (Iterator<Command> it = redoCommands.iterator(); it.hasNext();) {
[12718]217 if (it.next().getAffectedDataSet() == dataSet) {
[1169]218 it.remove();
219 changed = true;
220 }
221 }
[1814]222 if (changed) {
[1169]223 fireCommandsChanged();
[1814]224 }
[1169]225 }
[304]226
[6413]227 /**
228 * Removes a command queue listener.
229 * @param l The command queue listener to remove
230 */
[4908]231 public void removeCommandQueueListener(CommandQueueListener l) {
232 listenerCommands.remove(l);
233 }
234
[6413]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 */
[4908]240 public boolean addCommandQueueListener(CommandQueueListener l) {
241 return listenerCommands.add(l);
242 }
[301]243}
Note: See TracBrowser for help on using the repository browser.