source: josm/trunk/src/org/openstreetmap/josm/actions/MergeNodesAction.java@ 3049

Last change on this file since 3049 was 2842, checked in by mjulius, 14 years ago

fix messages for actions

  • Property svn:eol-style set to native
File size: 10.5 KB
RevLine 
[422]1//License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.actions;
3
[2220]4import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.combineTigerTags;
5import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.completeTagCollectionForEditing;
6import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.normalizeTagCollectionBeforeEditing;
[2315]7import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
[422]8import static org.openstreetmap.josm.tools.I18n.tr;
9
10import java.awt.event.ActionEvent;
11import java.awt.event.KeyEvent;
[582]12import java.util.ArrayList;
[422]13import java.util.Collection;
[582]14import java.util.HashSet;
[2341]15import java.util.LinkedHashSet;
[422]16import java.util.LinkedList;
[2113]17import java.util.List;
[422]18import java.util.Set;
19
20import javax.swing.JOptionPane;
21
22import org.openstreetmap.josm.Main;
23import org.openstreetmap.josm.command.ChangeCommand;
24import org.openstreetmap.josm.command.Command;
25import org.openstreetmap.josm.command.DeleteCommand;
26import org.openstreetmap.josm.command.SequenceCommand;
[582]27import org.openstreetmap.josm.data.osm.Node;
[422]28import org.openstreetmap.josm.data.osm.OsmPrimitive;
[2565]29import org.openstreetmap.josm.data.osm.RelationToChildReference;
[2095]30import org.openstreetmap.josm.data.osm.TagCollection;
[422]31import org.openstreetmap.josm.data.osm.Way;
[2315]32import org.openstreetmap.josm.gui.DefaultNameFormatter;
33import org.openstreetmap.josm.gui.HelpAwareOptionPane;
34import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
[2095]35import org.openstreetmap.josm.gui.conflict.tags.CombinePrimitiveResolverDialog;
36import org.openstreetmap.josm.gui.layer.OsmDataLayer;
[2842]37import org.openstreetmap.josm.tools.CheckParameterUtil;
[2315]38import org.openstreetmap.josm.tools.ImageProvider;
[1084]39import org.openstreetmap.josm.tools.Shortcut;
[422]40/**
[2095]41 * Merges a collection of nodes into one node.
[2512]42 *
[422]43 */
[1820]44public class MergeNodesAction extends JosmAction {
[422]45
[1169]46 public MergeNodesAction() {
47 super(tr("Merge Nodes"), "mergenodes", tr("Merge nodes into the oldest one."),
[1814]48 Shortcut.registerShortcut("tools:mergenodes", tr("Tool: {0}", tr("Merge Nodes")), KeyEvent.VK_M, Shortcut.GROUP_EDIT), true);
[2323]49 putValue("help", ht("/Action/MergeNodesAction"));
[1169]50 }
[422]51
[1169]52 public void actionPerformed(ActionEvent event) {
[1820]53 if (!isEnabled())
54 return;
[1814]55 Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
[2341]56 LinkedHashSet<Node> selectedNodes = OsmPrimitive.getFilteredSet(selection, Node.class);
[1169]57 if (selectedNodes.size() < 2) {
[2017]58 JOptionPane.showMessageDialog(
[1847]59 Main.parent,
60 tr("Please select at least two nodes to merge."),
61 tr("Warning"),
62 JOptionPane.WARNING_MESSAGE
63 );
[1169]64 return;
65 }
[422]66
[2095]67 Node targetNode = selectTargetNode(selectedNodes);
68 Command cmd = mergeNodes(Main.main.getEditLayer(), selectedNodes, targetNode);
69 if (cmd != null) {
70 Main.main.undoRedo.add(cmd);
71 Main.main.getEditLayer().data.setSelected(targetNode);
72 }
73 }
74
75 /**
[2341]76 * Find which node to merge into (i.e. which one will be left)
[2414]77 * The last selected node will become the target node the remaining
[2341]78 * nodes are merged to.
[2512]79 *
[2095]80 * @param candidates the collection of candidate nodes
81 * @return the selected target node
82 */
[2341]83 public static Node selectTargetNode(LinkedHashSet<Node> candidates) {
[2095]84 Node targetNode = null;
[2341]85 for (Node n : candidates) { // pick last one
86 targetNode = n;
[1169]87 }
[2095]88 return targetNode;
[1169]89 }
[422]90
[1169]91 /**
[2113]92 * Fixes the parent ways referring to one of the nodes.
[2512]93 *
[2202]94 * Replies null, if the ways could not be fixed, i.e. because a way would have to be deleted
[2113]95 * which is referred to by a relation.
[2512]96 *
[2113]97 * @param nodesToDelete the collection of nodes to be deleted
98 * @param targetNode the target node the other nodes are merged to
[2202]99 * @return a list of commands; null, if the ways could not be fixed
[2113]100 */
[2565]101 protected static List<Command> fixParentWays(Collection<OsmPrimitive> nodesToDelete, Node targetNode) {
[2113]102 List<Command> cmds = new ArrayList<Command>();
103 Set<Way> waysToDelete = new HashSet<Way>();
104
[2565]105 for (Way w: OsmPrimitive.getFilteredList(OsmPrimitive.getReferrer(nodesToDelete), Way.class)) {
[2113]106 ArrayList<Node> newNodes = new ArrayList<Node>(w.getNodesCount());
107 for (Node n: w.getNodes()) {
108 if (! nodesToDelete.contains(n) && n != targetNode) {
109 newNodes.add(n);
110 } else if (newNodes.isEmpty()) {
111 newNodes.add(targetNode);
112 } else if (newNodes.get(newNodes.size()-1) != targetNode) {
113 // make sure we collapse a sequence of deleted nodes
114 // to exactly one occurrence of the merged target node
115 //
116 newNodes.add(targetNode);
117 } else {
118 // drop the node
119 }
120 }
121 if (newNodes.size() < 2) {
[2565]122 if (w.getReferrers().isEmpty()) {
[2113]123 waysToDelete.add(w);
124 } else {
[2315]125 ButtonSpec[] options = new ButtonSpec[] {
126 new ButtonSpec(
127 tr("Abort Merging"),
128 ImageProvider.get("cancel"),
129 tr("Click to abort merging nodes"),
130 null /* no special help topic */
131 )
132 };
133 HelpAwareOptionPane.showOptionDialog(
[2113]134 Main.parent,
[2315]135 tr(
136 "Cannot merge nodes: Would have to delete way ''{0}'' which is still used.",
137 w.getDisplayName(DefaultNameFormatter.getInstance())
138 ),
[2113]139 tr("Warning"),
[2315]140 JOptionPane.WARNING_MESSAGE,
141 null, /* no icon */
142 options,
143 options[0],
144 ht("/Action/MergeNodes#WaysToDeleteStillInUse")
[2113]145 );
146 return null;
147 }
[2565]148 } else if(newNodes.size() < 2 && w.getReferrers().isEmpty()) {
[2113]149 waysToDelete.add(w);
150 } else {
151 Way newWay = new Way(w);
152 newWay.setNodes(newNodes);
153 cmds.add(new ChangeCommand(w, newWay));
154 }
155 }
[2333]156 if (!waysToDelete.isEmpty()) {
157 cmds.add(new DeleteCommand(waysToDelete));
158 }
[2113]159 return cmds;
160 }
161
162 /**
[2220]163 * Merges the nodes in <code>nodes</code> onto one of the nodes. Uses the dataset
[2565]164 * managed by <code>layer</code> as reference.
[2095]165 *
166 * @param layer layer the reference data layer. Must not be null.
167 * @param nodes the collection of nodes. Ignored if null.
168 * @param targetNode the target node the collection of nodes is merged to. Must not be null.
169 * @throw IllegalArgumentException thrown if layer is null
170 */
[2565]171 public static Command mergeNodes(OsmDataLayer layer,Collection<Node> nodes, Node targetNode) {
[2842]172 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
173 CheckParameterUtil.ensureParameterNotNull(targetNode, "targetNode");
[2095]174 if (nodes == null)
175 return null;
[422]176
[2565]177 Set<RelationToChildReference> relationToNodeReferences = RelationToChildReference.getRelationToChildReferences(nodes);
178
[2095]179 // build the tag collection
180 //
181 TagCollection nodeTags = TagCollection.unionOfAllPrimitives(nodes);
[2202]182 combineTigerTags(nodeTags);
[2220]183 normalizeTagCollectionBeforeEditing(nodeTags, nodes);
[2095]184 TagCollection nodeTagsToEdit = new TagCollection(nodeTags);
185 completeTagCollectionForEditing(nodeTagsToEdit);
[422]186
[2095]187 // launch a conflict resolution dialog, if necessary
188 //
189 CombinePrimitiveResolverDialog dialog = CombinePrimitiveResolverDialog.getInstance();
[2220]190 dialog.getTagConflictResolverModel().populate(nodeTagsToEdit, nodeTags.getKeysWithMultipleValues());
[2095]191 dialog.getRelationMemberConflictResolverModel().populate(relationToNodeReferences);
192 dialog.setTargetPrimitive(targetNode);
193 dialog.prepareDefaultDecisions();
[2220]194 // conflict resolution is necessary if there are conflicts in the merged tags
195 // or if at least one of the merged nodes is referred to by a relation
196 //
[2095]197 if (! nodeTags.isApplicableToPrimitive() || relationToNodeReferences.size() > 1) {
198 dialog.setVisible(true);
199 if (dialog.isCancelled())
[1169]200 return null;
201 }
202 LinkedList<Command> cmds = new LinkedList<Command>();
[422]203
[2095]204 // the nodes we will have to delete
205 //
206 Collection<OsmPrimitive> nodesToDelete = new HashSet<OsmPrimitive>(nodes);
207 nodesToDelete.remove(targetNode);
[2064]208
[2202]209 // fix the ways referring to at least one of the merged nodes
[2095]210 //
211 Collection<Way> waysToDelete= new HashSet<Way>();
[2113]212 List<Command> wayFixCommands = fixParentWays(
213 nodesToDelete,
214 targetNode
215 );
216 if (wayFixCommands == null)
217 return null;
[2220]218 cmds.addAll(wayFixCommands);
[422]219
[2095]220 // build the commands
221 //
222 if (!nodesToDelete.isEmpty()) {
223 cmds.add(new DeleteCommand(nodesToDelete));
[1814]224 }
[2095]225 if (!waysToDelete.isEmpty()) {
226 cmds.add(new DeleteCommand(waysToDelete));
[1169]227 }
[2095]228 cmds.addAll(dialog.buildResolutionCommands());
229 Command cmd = new SequenceCommand(tr("Merge {0} nodes", nodes.size()), cmds);
230 return cmd;
[1169]231 }
[422]232
[1820]233 @Override
[2256]234 protected void updateEnabledState() {
235 if (getCurrentDataSet() == null) {
[1820]236 setEnabled(false);
[2256]237 } else {
238 updateEnabledState(getCurrentDataSet().getSelected());
239 }
240 }
241
242 @Override
243 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
244 if (selection == null || selection.isEmpty()) {
245 setEnabled(false);
[1820]246 return;
247 }
[1169]248 boolean ok = true;
[2256]249 if (selection.size() < 2) {
[1169]250 setEnabled(false);
251 return;
252 }
[2256]253 for (OsmPrimitive osm : selection) {
[1169]254 if (!(osm instanceof Node)) {
255 ok = false;
256 break;
257 }
258 }
259 setEnabled(ok);
260 }
[422]261}
Note: See TracBrowser for help on using the repository browser.