| 1 | //License: GPL. Copyright 2007 by Immanuel Scholz and others |
|---|
| 2 | package org.openstreetmap.josm.command; |
|---|
| 3 | |
|---|
| 4 | import static org.openstreetmap.josm.tools.I18n.tr; |
|---|
| 5 | |
|---|
| 6 | import java.awt.GridBagLayout; |
|---|
| 7 | import java.awt.geom.Area; |
|---|
| 8 | import java.util.ArrayList; |
|---|
| 9 | import java.util.Collection; |
|---|
| 10 | import java.util.HashMap; |
|---|
| 11 | import java.util.LinkedHashMap; |
|---|
| 12 | import java.util.Map; |
|---|
| 13 | import java.util.Map.Entry; |
|---|
| 14 | |
|---|
| 15 | import javax.swing.JLabel; |
|---|
| 16 | import javax.swing.JOptionPane; |
|---|
| 17 | import javax.swing.JPanel; |
|---|
| 18 | import javax.swing.tree.DefaultMutableTreeNode; |
|---|
| 19 | import javax.swing.tree.MutableTreeNode; |
|---|
| 20 | |
|---|
| 21 | import org.openstreetmap.josm.Main; |
|---|
| 22 | import org.openstreetmap.josm.data.osm.Node; |
|---|
| 23 | import org.openstreetmap.josm.data.osm.OsmPrimitive; |
|---|
| 24 | import org.openstreetmap.josm.data.osm.PrimitiveData; |
|---|
| 25 | import org.openstreetmap.josm.data.osm.Relation; |
|---|
| 26 | import org.openstreetmap.josm.data.osm.Way; |
|---|
| 27 | import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor; |
|---|
| 28 | import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil; |
|---|
| 29 | import org.openstreetmap.josm.gui.layer.Layer; |
|---|
| 30 | import org.openstreetmap.josm.gui.layer.OsmDataLayer; |
|---|
| 31 | import org.openstreetmap.josm.tools.CheckParameterUtil; |
|---|
| 32 | |
|---|
| 33 | /** |
|---|
| 34 | * Classes implementing Command modify a dataset in a specific way. A command is |
|---|
| 35 | * one atomic action on a specific dataset, such as move or delete. |
|---|
| 36 | * |
|---|
| 37 | * The command remembers the {@see OsmDataLayer} it is operating on. |
|---|
| 38 | * |
|---|
| 39 | * @author imi |
|---|
| 40 | */ |
|---|
| 41 | abstract public class Command extends PseudoCommand { |
|---|
| 42 | |
|---|
| 43 | private static final class CloneVisitor extends AbstractVisitor { |
|---|
| 44 | public final Map<OsmPrimitive, PrimitiveData> orig = new LinkedHashMap<OsmPrimitive, PrimitiveData>(); |
|---|
| 45 | |
|---|
| 46 | public void visit(Node n) { |
|---|
| 47 | orig.put(n, n.save()); |
|---|
| 48 | } |
|---|
| 49 | public void visit(Way w) { |
|---|
| 50 | orig.put(w, w.save()); |
|---|
| 51 | } |
|---|
| 52 | public void visit(Relation e) { |
|---|
| 53 | orig.put(e, e.save()); |
|---|
| 54 | } |
|---|
| 55 | } |
|---|
| 56 | |
|---|
| 57 | /** the map of OsmPrimitives in the original state to OsmPrimitives in cloned state */ |
|---|
| 58 | private Map<OsmPrimitive, PrimitiveData> cloneMap = new HashMap<OsmPrimitive, PrimitiveData>(); |
|---|
| 59 | |
|---|
| 60 | /** the layer which this command is applied to */ |
|---|
| 61 | private OsmDataLayer layer; |
|---|
| 62 | |
|---|
| 63 | public Command() { |
|---|
| 64 | this.layer = Main.map.mapView.getEditLayer(); |
|---|
| 65 | } |
|---|
| 66 | |
|---|
| 67 | /** |
|---|
| 68 | * Creates a new command in the context of a specific data layer |
|---|
| 69 | * |
|---|
| 70 | * @param layer the data layer. Must not be null. |
|---|
| 71 | * @throws IllegalArgumentException thrown if layer is null |
|---|
| 72 | */ |
|---|
| 73 | public Command(OsmDataLayer layer) throws IllegalArgumentException { |
|---|
| 74 | CheckParameterUtil.ensureParameterNotNull(layer, "layer"); |
|---|
| 75 | this.layer = layer; |
|---|
| 76 | } |
|---|
| 77 | |
|---|
| 78 | /** |
|---|
| 79 | * Executes the command on the dataset. This implementation will remember all |
|---|
| 80 | * primitives returned by fillModifiedData for restoring them on undo. |
|---|
| 81 | */ |
|---|
| 82 | public boolean executeCommand() { |
|---|
| 83 | CloneVisitor visitor = new CloneVisitor(); |
|---|
| 84 | Collection<OsmPrimitive> all = new ArrayList<OsmPrimitive>(); |
|---|
| 85 | fillModifiedData(all, all, all); |
|---|
| 86 | for (OsmPrimitive osm : all) { |
|---|
| 87 | osm.visit(visitor); |
|---|
| 88 | } |
|---|
| 89 | cloneMap = visitor.orig; |
|---|
| 90 | return true; |
|---|
| 91 | } |
|---|
| 92 | |
|---|
| 93 | /** |
|---|
| 94 | * Undoes the command. |
|---|
| 95 | * It can be assumed that all objects are in the same state they were before. |
|---|
| 96 | * It can also be assumed that executeCommand was called exactly once before. |
|---|
| 97 | * |
|---|
| 98 | * This implementation undoes all objects stored by a former call to executeCommand. |
|---|
| 99 | */ |
|---|
| 100 | public void undoCommand() { |
|---|
| 101 | for (Entry<OsmPrimitive, PrimitiveData> e : cloneMap.entrySet()) { |
|---|
| 102 | OsmPrimitive primitive = e.getKey(); |
|---|
| 103 | if (primitive.getDataSet() != null) { |
|---|
| 104 | e.getKey().load(e.getValue()); |
|---|
| 105 | } |
|---|
| 106 | } |
|---|
| 107 | } |
|---|
| 108 | |
|---|
| 109 | /** |
|---|
| 110 | * Called when a layer has been removed to have the command remove itself from |
|---|
| 111 | * any buffer if it is not longer applicable to the dataset (e.g. it was part of |
|---|
| 112 | * the removed layer) |
|---|
| 113 | * |
|---|
| 114 | * @param oldLayer the old layer |
|---|
| 115 | * @return true if this command |
|---|
| 116 | */ |
|---|
| 117 | public boolean invalidBecauselayerRemoved(Layer oldLayer) { |
|---|
| 118 | if (!(oldLayer instanceof OsmDataLayer)) |
|---|
| 119 | return false; |
|---|
| 120 | return layer == oldLayer; |
|---|
| 121 | } |
|---|
| 122 | |
|---|
| 123 | /** |
|---|
| 124 | * Lets other commands access the original version |
|---|
| 125 | * of the object. Usually for undoing. |
|---|
| 126 | */ |
|---|
| 127 | public PrimitiveData getOrig(OsmPrimitive osm) { |
|---|
| 128 | PrimitiveData o = cloneMap.get(osm); |
|---|
| 129 | if (o != null) |
|---|
| 130 | return o; |
|---|
| 131 | for (OsmPrimitive t : cloneMap.keySet()) { |
|---|
| 132 | PrimitiveData to = cloneMap.get(t); |
|---|
| 133 | } |
|---|
| 134 | return o; |
|---|
| 135 | } |
|---|
| 136 | |
|---|
| 137 | /** |
|---|
| 138 | * Replies the layer this command is (or was) applied to. |
|---|
| 139 | * |
|---|
| 140 | */ |
|---|
| 141 | protected OsmDataLayer getLayer() { |
|---|
| 142 | return layer; |
|---|
| 143 | } |
|---|
| 144 | |
|---|
| 145 | /** |
|---|
| 146 | * Fill in the changed data this command operates on. |
|---|
| 147 | * Add to the lists, don't clear them. |
|---|
| 148 | * |
|---|
| 149 | * @param modified The modified primitives |
|---|
| 150 | * @param deleted The deleted primitives |
|---|
| 151 | * @param added The added primitives |
|---|
| 152 | */ |
|---|
| 153 | abstract public void fillModifiedData(Collection<OsmPrimitive> modified, |
|---|
| 154 | Collection<OsmPrimitive> deleted, |
|---|
| 155 | Collection<OsmPrimitive> added); |
|---|
| 156 | |
|---|
| 157 | /** |
|---|
| 158 | * Return the primitives that take part in this command. |
|---|
| 159 | */ |
|---|
| 160 | @Override public Collection<? extends OsmPrimitive> getParticipatingPrimitives() { |
|---|
| 161 | return cloneMap.keySet(); |
|---|
| 162 | } |
|---|
| 163 | |
|---|
| 164 | /** |
|---|
| 165 | * Check whether user is about to operate on data outside of the download area. |
|---|
| 166 | * Request confirmation if he is. |
|---|
| 167 | * |
|---|
| 168 | * @param operation the operation name which is used for setting some preferences |
|---|
| 169 | * @param dialogTitle the title of the dialog being displayed |
|---|
| 170 | * @param outsideDialogMessage the message text to be displayed when data is outside of the download area |
|---|
| 171 | * @param incompleteDialogMessage the message text to be displayed when data is incomplete |
|---|
| 172 | * @param area the area used to determine whether data is outlying |
|---|
| 173 | * @param primitives the primitives to operate on |
|---|
| 174 | * @param ignore {@code null} or a primitive to be ignored |
|---|
| 175 | * @return true, if operating on outlying primitives is OK; false, otherwise |
|---|
| 176 | */ |
|---|
| 177 | public static boolean checkAndConfirmOutlyingOperation(String operation, |
|---|
| 178 | String dialogTitle, String outsideDialogMessage, String incompleteDialogMessage, |
|---|
| 179 | Area area, Collection<? extends OsmPrimitive> primitives, |
|---|
| 180 | Collection<? extends OsmPrimitive> ignore) { |
|---|
| 181 | boolean outside = false; |
|---|
| 182 | boolean incomplete = false; |
|---|
| 183 | for (OsmPrimitive osm : primitives) { |
|---|
| 184 | if (osm.isIncomplete()) { |
|---|
| 185 | incomplete = true; |
|---|
| 186 | } else if (area != null && isOutlying(osm, area) |
|---|
| 187 | && (ignore == null || !ignore.contains(osm))) { |
|---|
| 188 | outside = true; |
|---|
| 189 | } |
|---|
| 190 | } |
|---|
| 191 | if (outside) { |
|---|
| 192 | JPanel msg = new JPanel(new GridBagLayout()); |
|---|
| 193 | msg.add(new JLabel("<html>" + outsideDialogMessage + "</html>")); |
|---|
| 194 | boolean answer = ConditionalOptionPaneUtil.showConfirmationDialog( |
|---|
| 195 | operation + "_outside_nodes", |
|---|
| 196 | Main.parent, |
|---|
| 197 | msg, |
|---|
| 198 | dialogTitle, |
|---|
| 199 | JOptionPane.YES_NO_OPTION, |
|---|
| 200 | JOptionPane.QUESTION_MESSAGE, |
|---|
| 201 | JOptionPane.YES_OPTION); |
|---|
| 202 | if(!answer) |
|---|
| 203 | return false; |
|---|
| 204 | } |
|---|
| 205 | if (incomplete) { |
|---|
| 206 | JPanel msg = new JPanel(new GridBagLayout()); |
|---|
| 207 | msg.add(new JLabel("<html>" + incompleteDialogMessage + "</html>")); |
|---|
| 208 | boolean answer = ConditionalOptionPaneUtil.showConfirmationDialog( |
|---|
| 209 | operation + "_incomplete", |
|---|
| 210 | Main.parent, |
|---|
| 211 | msg, |
|---|
| 212 | dialogTitle, |
|---|
| 213 | JOptionPane.YES_NO_OPTION, |
|---|
| 214 | JOptionPane.QUESTION_MESSAGE, |
|---|
| 215 | JOptionPane.YES_OPTION); |
|---|
| 216 | if(!answer) |
|---|
| 217 | return false; |
|---|
| 218 | } |
|---|
| 219 | return true; |
|---|
| 220 | } |
|---|
| 221 | |
|---|
| 222 | private static boolean isOutlying(OsmPrimitive osm, Area area) { |
|---|
| 223 | if (osm instanceof Node) { |
|---|
| 224 | return !osm.isNewOrUndeleted() && !area.contains(((Node) osm).getCoor()); |
|---|
| 225 | } else if (osm instanceof Way) { |
|---|
| 226 | for (Node n : ((Way) osm).getNodes()) { |
|---|
| 227 | if (isOutlying(n, area)) { |
|---|
| 228 | return true; |
|---|
| 229 | } |
|---|
| 230 | } |
|---|
| 231 | return false; |
|---|
| 232 | } |
|---|
| 233 | return false; |
|---|
| 234 | } |
|---|
| 235 | } |
|---|