001 //License: GPL. Copyright 2007 by Immanuel Scholz and others 002 package org.openstreetmap.josm.command; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 006 import java.awt.GridBagLayout; 007 import java.awt.geom.Area; 008 import java.util.ArrayList; 009 import java.util.Collection; 010 import java.util.HashMap; 011 import java.util.LinkedHashMap; 012 import java.util.Map; 013 import java.util.Map.Entry; 014 015 import javax.swing.JLabel; 016 import javax.swing.JOptionPane; 017 import javax.swing.JPanel; 018 import javax.swing.tree.DefaultMutableTreeNode; 019 import javax.swing.tree.MutableTreeNode; 020 021 import org.openstreetmap.josm.Main; 022 import org.openstreetmap.josm.data.osm.Node; 023 import org.openstreetmap.josm.data.osm.OsmPrimitive; 024 import org.openstreetmap.josm.data.osm.PrimitiveData; 025 import org.openstreetmap.josm.data.osm.Relation; 026 import org.openstreetmap.josm.data.osm.Way; 027 import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor; 028 import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil; 029 import org.openstreetmap.josm.gui.layer.Layer; 030 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 031 import org.openstreetmap.josm.tools.CheckParameterUtil; 032 033 /** 034 * Classes implementing Command modify a dataset in a specific way. A command is 035 * one atomic action on a specific dataset, such as move or delete. 036 * 037 * The command remembers the {@link OsmDataLayer} it is operating on. 038 * 039 * @author imi 040 */ 041 abstract public class Command extends PseudoCommand { 042 043 private static final class CloneVisitor extends AbstractVisitor { 044 public final Map<OsmPrimitive, PrimitiveData> orig = new LinkedHashMap<OsmPrimitive, PrimitiveData>(); 045 046 public void visit(Node n) { 047 orig.put(n, n.save()); 048 } 049 public void visit(Way w) { 050 orig.put(w, w.save()); 051 } 052 public void visit(Relation e) { 053 orig.put(e, e.save()); 054 } 055 } 056 057 /** the map of OsmPrimitives in the original state to OsmPrimitives in cloned state */ 058 private Map<OsmPrimitive, PrimitiveData> cloneMap = new HashMap<OsmPrimitive, PrimitiveData>(); 059 060 /** the layer which this command is applied to */ 061 private OsmDataLayer layer; 062 063 public Command() { 064 this.layer = Main.map.mapView.getEditLayer(); 065 } 066 067 /** 068 * Creates a new command in the context of a specific data layer 069 * 070 * @param layer the data layer. Must not be null. 071 * @throws IllegalArgumentException thrown if layer is null 072 */ 073 public Command(OsmDataLayer layer) throws IllegalArgumentException { 074 CheckParameterUtil.ensureParameterNotNull(layer, "layer"); 075 this.layer = layer; 076 } 077 078 /** 079 * Executes the command on the dataset. This implementation will remember all 080 * primitives returned by fillModifiedData for restoring them on undo. 081 */ 082 public boolean executeCommand() { 083 CloneVisitor visitor = new CloneVisitor(); 084 Collection<OsmPrimitive> all = new ArrayList<OsmPrimitive>(); 085 fillModifiedData(all, all, all); 086 for (OsmPrimitive osm : all) { 087 osm.visit(visitor); 088 } 089 cloneMap = visitor.orig; 090 return true; 091 } 092 093 /** 094 * Undoes the command. 095 * It can be assumed that all objects are in the same state they were before. 096 * It can also be assumed that executeCommand was called exactly once before. 097 * 098 * This implementation undoes all objects stored by a former call to executeCommand. 099 */ 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 }