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