[8378] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
[301] | 2 | package org.openstreetmap.josm.data;
|
---|
| 3 |
|
---|
[2539] | 4 | import java.util.Collection;
|
---|
[304] | 5 | import java.util.Iterator;
|
---|
[301] | 6 | import java.util.LinkedList;
|
---|
[11553] | 7 | import java.util.Optional;
|
---|
[301] | 8 |
|
---|
| 9 | import org.openstreetmap.josm.Main;
|
---|
| 10 | import org.openstreetmap.josm.command.Command;
|
---|
[6994] | 11 | import org.openstreetmap.josm.data.osm.DataSet;
|
---|
[2539] | 12 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
---|
[6413] | 13 | import 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] | 20 | public 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.
|
---|
[12841] | 76 | if (commands.size() > Main.pref.getInt("undo.max", 1000)) {
|
---|
[3143] | 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 | }
|
---|