001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.datatransfer.importers; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.datatransfer.UnsupportedFlavorException; 007import java.io.IOException; 008import java.util.ArrayList; 009import java.util.EnumMap; 010import java.util.HashMap; 011import java.util.List; 012import java.util.Map; 013 014import javax.swing.TransferHandler.TransferSupport; 015 016import org.openstreetmap.josm.Main; 017import org.openstreetmap.josm.command.AddPrimitivesCommand; 018import org.openstreetmap.josm.data.coor.EastNorth; 019import org.openstreetmap.josm.data.osm.NodeData; 020import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 021import org.openstreetmap.josm.data.osm.PrimitiveData; 022import org.openstreetmap.josm.data.osm.RelationData; 023import org.openstreetmap.josm.data.osm.RelationMemberData; 024import org.openstreetmap.josm.data.osm.WayData; 025import org.openstreetmap.josm.gui.ExtendedDialog; 026import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData; 027import org.openstreetmap.josm.gui.layer.OsmDataLayer; 028import org.openstreetmap.josm.tools.bugreport.BugReport; 029 030/** 031 * This transfer support allows us to transfer primitives. This is the default paste action when primitives were copied. 032 * @author Michael Zangl 033 * @since 10604 034 */ 035public final class PrimitiveDataPaster extends AbstractOsmDataPaster { 036 /** 037 * Create a new {@link PrimitiveDataPaster} 038 */ 039 public PrimitiveDataPaster() { 040 super(PrimitiveTransferData.DATA_FLAVOR); 041 } 042 043 @Override 044 public boolean importData(TransferSupport support, final OsmDataLayer layer, EastNorth pasteAt) 045 throws UnsupportedFlavorException, IOException { 046 PrimitiveTransferData pasteBuffer = (PrimitiveTransferData) support.getTransferable().getTransferData(df); 047 // Allow to cancel paste if there are incomplete primitives 048 if (pasteBuffer.hasIncompleteData() && !confirmDeleteIncomplete()) { 049 return false; 050 } 051 052 EastNorth center = pasteBuffer.getCenter(); 053 EastNorth offset = center == null || pasteAt == null ? new EastNorth(0, 0) : pasteAt.subtract(center); 054 055 AddPrimitivesCommand command = createNewPrimitives(pasteBuffer, offset, layer); 056 057 /* Now execute the commands to add the duplicated contents of the paste buffer to the map */ 058 Main.main.undoRedo.add(command); 059 return true; 060 } 061 062 private static AddPrimitivesCommand createNewPrimitives(PrimitiveTransferData pasteBuffer, EastNorth offset, OsmDataLayer layer) { 063 // Make a copy of pasteBuffer and map from old id to copied data id 064 List<PrimitiveData> bufferCopy = new ArrayList<>(); 065 List<PrimitiveData> toSelect = new ArrayList<>(); 066 EnumMap<OsmPrimitiveType, Map<Long, Long>> newIds = generateNewPrimitives(pasteBuffer, bufferCopy, toSelect); 067 068 // Update references in copied buffer 069 for (PrimitiveData data : bufferCopy) { 070 try { 071 if (data instanceof NodeData) { 072 NodeData nodeData = (NodeData) data; 073 nodeData.setEastNorth(nodeData.getEastNorth().add(offset)); 074 } else if (data instanceof WayData) { 075 updateNodes(newIds.get(OsmPrimitiveType.NODE), data); 076 } else if (data instanceof RelationData) { 077 updateMembers(newIds, data); 078 } 079 } catch (RuntimeException e) { 080 throw BugReport.intercept(e).put("data", data); 081 } 082 } 083 return new AddPrimitivesCommand(bufferCopy, toSelect, layer); 084 } 085 086 private static EnumMap<OsmPrimitiveType, Map<Long, Long>> generateNewPrimitives(PrimitiveTransferData pasteBuffer, 087 List<PrimitiveData> bufferCopy, List<PrimitiveData> toSelect) { 088 EnumMap<OsmPrimitiveType, Map<Long, Long>> newIds = new EnumMap<>(OsmPrimitiveType.class); 089 newIds.put(OsmPrimitiveType.NODE, new HashMap<Long, Long>()); 090 newIds.put(OsmPrimitiveType.WAY, new HashMap<Long, Long>()); 091 newIds.put(OsmPrimitiveType.RELATION, new HashMap<Long, Long>()); 092 093 for (PrimitiveData data : pasteBuffer.getAll()) { 094 if (data.isIncomplete() || !data.isVisible()) { 095 continue; 096 } 097 PrimitiveData copy = data.makeCopy(); 098 // don't know why this is reset, but we need it to not crash on copying incomplete nodes. 099 boolean wasIncomplete = copy.isIncomplete(); 100 copy.clearOsmMetadata(); 101 copy.setIncomplete(wasIncomplete); 102 newIds.get(data.getType()).put(data.getUniqueId(), copy.getUniqueId()); 103 104 bufferCopy.add(copy); 105 if (pasteBuffer.getDirectlyAdded().contains(data)) { 106 toSelect.add(copy); 107 } 108 } 109 return newIds; 110 } 111 112 private static void updateMembers(EnumMap<OsmPrimitiveType, Map<Long, Long>> newIds, PrimitiveData data) { 113 List<RelationMemberData> newMembers = new ArrayList<>(); 114 for (RelationMemberData member : ((RelationData) data).getMembers()) { 115 OsmPrimitiveType memberType = member.getMemberType(); 116 Long newId = newIds.get(memberType).get(member.getMemberId()); 117 if (newId != null) { 118 newMembers.add(new RelationMemberData(member.getRole(), memberType, newId)); 119 } 120 } 121 ((RelationData) data).setMembers(newMembers); 122 } 123 124 private static void updateNodes(Map<Long, Long> newNodeIds, PrimitiveData data) { 125 List<Long> newNodes = new ArrayList<>(); 126 for (Long oldNodeId : ((WayData) data).getNodes()) { 127 Long newNodeId = newNodeIds.get(oldNodeId); 128 if (newNodeId != null) { 129 newNodes.add(newNodeId); 130 } 131 } 132 ((WayData) data).setNodes(newNodes); 133 } 134 135 private static boolean confirmDeleteIncomplete() { 136 ExtendedDialog ed = new ExtendedDialog(Main.parent, tr("Delete incomplete members?"), 137 new String[] {tr("Paste without incomplete members"), tr("Cancel")}); 138 ed.setButtonIcons(new String[] {"dialogs/relation/deletemembers", "cancel"}); 139 ed.setContent(tr( 140 "The copied data contains incomplete objects. " + "When pasting the incomplete objects are removed. " 141 + "Do you want to paste the data without the incomplete objects?")); 142 ed.showDialog(); 143 return ed.getValue() == 1; 144 } 145}