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}