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