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 }