// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.datatransfer.importers; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.datatransfer.UnsupportedFlavorException; import java.io.IOException; import java.util.ArrayList; import java.util.EnumMap; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.TransferHandler.TransferSupport; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.command.AddPrimitivesCommand; import org.openstreetmap.josm.data.coor.EastNorth; import org.openstreetmap.josm.data.osm.NodeData; import org.openstreetmap.josm.data.osm.OsmPrimitiveType; import org.openstreetmap.josm.data.osm.PrimitiveData; import org.openstreetmap.josm.data.osm.RelationData; import org.openstreetmap.josm.data.osm.RelationMemberData; import org.openstreetmap.josm.data.osm.WayData; import org.openstreetmap.josm.gui.ExtendedDialog; import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData; import org.openstreetmap.josm.gui.layer.OsmDataLayer; import org.openstreetmap.josm.tools.JosmRuntimeException; import org.openstreetmap.josm.tools.bugreport.BugReport; /** * This transfer support allows us to transfer primitives. This is the default paste action when primitives were copied. * @author Michael Zangl * @since 10604 */ public final class PrimitiveDataPaster extends AbstractOsmDataPaster { /** * Create a new {@link PrimitiveDataPaster} */ public PrimitiveDataPaster() { super(PrimitiveTransferData.DATA_FLAVOR); } @Override public boolean importData(TransferSupport support, final OsmDataLayer layer, EastNorth pasteAt) throws UnsupportedFlavorException, IOException { PrimitiveTransferData pasteBuffer = (PrimitiveTransferData) support.getTransferable().getTransferData(df); // Allow to cancel paste if there are incomplete primitives if (pasteBuffer.hasIncompleteData() && !confirmDeleteIncomplete()) { return false; } EastNorth center = pasteBuffer.getCenter(); EastNorth offset = center == null || pasteAt == null ? new EastNorth(0, 0) : pasteAt.subtract(center); AddPrimitivesCommand command = createNewPrimitives(pasteBuffer, offset, layer); /* Now execute the commands to add the duplicated contents of the paste buffer to the map */ Main.main.undoRedo.add(command); return true; } private static AddPrimitivesCommand createNewPrimitives(PrimitiveTransferData pasteBuffer, EastNorth offset, OsmDataLayer layer) { // Make a copy of pasteBuffer and map from old id to copied data id List<PrimitiveData> bufferCopy = new ArrayList<>(); List<PrimitiveData> toSelect = new ArrayList<>(); EnumMap<OsmPrimitiveType, Map<Long, Long>> newIds = generateNewPrimitives(pasteBuffer, bufferCopy, toSelect); // Update references in copied buffer for (PrimitiveData data : bufferCopy) { try { if (data instanceof NodeData) { NodeData nodeData = (NodeData) data; nodeData.setEastNorth(nodeData.getEastNorth().add(offset)); } else if (data instanceof WayData) { updateNodes(newIds.get(OsmPrimitiveType.NODE), data); } else if (data instanceof RelationData) { updateMembers(newIds, data); } } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) { throw BugReport.intercept(e).put("data", data); } } return new AddPrimitivesCommand(bufferCopy, toSelect, layer); } private static EnumMap<OsmPrimitiveType, Map<Long, Long>> generateNewPrimitives(PrimitiveTransferData pasteBuffer, List<PrimitiveData> bufferCopy, List<PrimitiveData> toSelect) { EnumMap<OsmPrimitiveType, Map<Long, Long>> newIds = new EnumMap<>(OsmPrimitiveType.class); newIds.put(OsmPrimitiveType.NODE, new HashMap<Long, Long>()); newIds.put(OsmPrimitiveType.WAY, new HashMap<Long, Long>()); newIds.put(OsmPrimitiveType.RELATION, new HashMap<Long, Long>()); for (PrimitiveData data : pasteBuffer.getAll()) { if (data.isIncomplete() || !data.isVisible()) { continue; } PrimitiveData copy = data.makeCopy(); // don't know why this is reset, but we need it to not crash on copying incomplete nodes. boolean wasIncomplete = copy.isIncomplete(); copy.clearOsmMetadata(); copy.setIncomplete(wasIncomplete); newIds.get(data.getType()).put(data.getUniqueId(), copy.getUniqueId()); bufferCopy.add(copy); if (pasteBuffer.getDirectlyAdded().contains(data)) { toSelect.add(copy); } } return newIds; } private static void updateMembers(EnumMap<OsmPrimitiveType, Map<Long, Long>> newIds, PrimitiveData data) { List<RelationMemberData> newMembers = new ArrayList<>(); for (RelationMemberData member : ((RelationData) data).getMembers()) { OsmPrimitiveType memberType = member.getMemberType(); Long newId = newIds.get(memberType).get(member.getMemberId()); if (newId != null) { newMembers.add(new RelationMemberData(member.getRole(), memberType, newId)); } } ((RelationData) data).setMembers(newMembers); } private static void updateNodes(Map<Long, Long> newNodeIds, PrimitiveData data) { List<Long> newNodes = new ArrayList<>(); for (Long oldNodeId : ((WayData) data).getNodes()) { Long newNodeId = newNodeIds.get(oldNodeId); if (newNodeId != null) { newNodes.add(newNodeId); } } ((WayData) data).setNodes(newNodes); } private static boolean confirmDeleteIncomplete() { ExtendedDialog ed = new ExtendedDialog(Main.parent, tr("Delete incomplete members?"), new String[] {tr("Paste without incomplete members"), tr("Cancel")}); ed.setButtonIcons(new String[] {"dialogs/relation/deletemembers", "cancel"}); ed.setContent(tr( "The copied data contains incomplete objects. " + "When pasting the incomplete objects are removed. " + "Do you want to paste the data without the incomplete objects?")); ed.showDialog(); return ed.getValue() == 1; } }