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