001    // License: GPL. Copyright 2007 by Immanuel Scholz and others
002    // Author: David Earl
003    package org.openstreetmap.josm.actions;
004    
005    import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
006    import static org.openstreetmap.josm.tools.I18n.tr;
007    
008    import java.awt.MouseInfo;
009    import java.awt.Point;
010    import java.awt.event.ActionEvent;
011    import java.awt.event.KeyEvent;
012    import java.util.ArrayList;
013    import java.util.HashMap;
014    import java.util.List;
015    import java.util.Map;
016    
017    import org.openstreetmap.josm.Main;
018    import org.openstreetmap.josm.command.AddPrimitivesCommand;
019    import org.openstreetmap.josm.data.coor.EastNorth;
020    import org.openstreetmap.josm.data.osm.NodeData;
021    import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
022    import org.openstreetmap.josm.data.osm.PrimitiveData;
023    import org.openstreetmap.josm.data.osm.PrimitiveDeepCopy;
024    import org.openstreetmap.josm.data.osm.RelationData;
025    import org.openstreetmap.josm.data.osm.RelationMemberData;
026    import org.openstreetmap.josm.data.osm.WayData;
027    import org.openstreetmap.josm.data.osm.PrimitiveDeepCopy.PasteBufferChangedListener;
028    import org.openstreetmap.josm.gui.ExtendedDialog;
029    import org.openstreetmap.josm.gui.layer.Layer;
030    import org.openstreetmap.josm.tools.Shortcut;
031    
032    public final class PasteAction extends JosmAction implements PasteBufferChangedListener {
033    
034        public PasteAction() {
035            super(tr("Paste"), "paste", tr("Paste contents of paste buffer."),
036                    Shortcut.registerShortcut("system:paste", tr("Edit: {0}", tr("Paste")), KeyEvent.VK_V, Shortcut.CTRL), true);
037            putValue("help", ht("/Action/Paste"));
038            Main.pasteBuffer.addPasteBufferChangedListener(this);
039        }
040    
041        public void actionPerformed(ActionEvent e) {
042            if (!isEnabled())
043                return;
044            pasteData(Main.pasteBuffer, Main.pasteSource, e);
045        }
046    
047        public  void pasteData(PrimitiveDeepCopy pasteBuffer, Layer source, ActionEvent e) {
048            /* Find the middle of the pasteBuffer area */
049            double maxEast = -1E100, minEast = 1E100, maxNorth = -1E100, minNorth = 1E100;
050            boolean incomplete = false;
051            for (PrimitiveData data : pasteBuffer.getAll()) {
052                if (data instanceof NodeData) {
053                    NodeData n = (NodeData)data;
054                    if (n.getEastNorth() != null) {
055                        double east = n.getEastNorth().east();
056                        double north = n.getEastNorth().north();
057                        if (east > maxEast) { maxEast = east; }
058                        if (east < minEast) { minEast = east; }
059                        if (north > maxNorth) { maxNorth = north; }
060                        if (north < minNorth) { minNorth = north; }
061                    }
062                }
063                if (data.isIncomplete()) {
064                    incomplete = true;
065                }
066            }
067    
068            // Allow to cancel paste if there are incomplete primitives
069            if (incomplete) {
070                if (!confirmDeleteIncomplete()) return;
071            }
072    
073            // default to paste in center of map (pasted via menu or cursor not in MapView)
074            EastNorth mPosition = Main.map.mapView.getCenter();
075            if((e.getModifiers() & ActionEvent.CTRL_MASK) != 0) {
076                final Point mp = MouseInfo.getPointerInfo().getLocation();
077                final Point tl = Main.map.mapView.getLocationOnScreen();
078                final Point pos = new Point(mp.x-tl.x, mp.y-tl.y);
079                if(Main.map.mapView.contains(pos)) {
080                    mPosition = Main.map.mapView.getEastNorth(pos.x, pos.y);
081                }
082            }
083    
084            double offsetEast  = mPosition.east() - (maxEast + minEast)/2.0;
085            double offsetNorth = mPosition.north() - (maxNorth + minNorth)/2.0;
086    
087            // Make a copy of pasteBuffer and map from old id to copied data id
088            List<PrimitiveData> bufferCopy = new ArrayList<PrimitiveData>();
089            Map<Long, Long> newNodeIds = new HashMap<Long, Long>();
090            Map<Long, Long> newWayIds = new HashMap<Long, Long>();
091            Map<Long, Long> newRelationIds = new HashMap<Long, Long>();
092            for (PrimitiveData data: pasteBuffer.getAll()) {
093                if (data.isIncomplete()) {
094                    continue;
095                }
096                PrimitiveData copy = data.makeCopy();
097                copy.clearOsmId();
098                if (data instanceof NodeData) {
099                    newNodeIds.put(data.getUniqueId(), copy.getUniqueId());
100                } else if (data instanceof WayData) {
101                    newWayIds.put(data.getUniqueId(), copy.getUniqueId());
102                } else if (data instanceof RelationData) {
103                    newRelationIds.put(data.getUniqueId(), copy.getUniqueId());
104                }
105                bufferCopy.add(copy);
106            }
107    
108            // Update references in copied buffer
109            for (PrimitiveData data:bufferCopy) {
110                if (data instanceof NodeData) {
111                    NodeData nodeData = (NodeData)data;
112                    if (Main.map.mapView.getEditLayer() == source) {
113                        nodeData.setEastNorth(nodeData.getEastNorth().add(offsetEast, offsetNorth));
114                    }
115                } else if (data instanceof WayData) {
116                    List<Long> newNodes = new ArrayList<Long>();
117                    for (Long oldNodeId: ((WayData)data).getNodes()) {
118                        Long newNodeId = newNodeIds.get(oldNodeId);
119                        if (newNodeId != null) {
120                            newNodes.add(newNodeId);
121                        }
122                    }
123                    ((WayData)data).setNodes(newNodes);
124                } else if (data instanceof RelationData) {
125                    List<RelationMemberData> newMembers = new ArrayList<RelationMemberData>();
126                    for (RelationMemberData member: ((RelationData)data).getMembers()) {
127                        OsmPrimitiveType memberType = member.getMemberType();
128                        Long newId = null;
129                        switch (memberType) {
130                        case NODE:
131                            newId = newNodeIds.get(member.getMemberId());
132                            break;
133                        case WAY:
134                            newId = newWayIds.get(member.getMemberId());
135                            break;
136                        case RELATION:
137                            newId = newRelationIds.get(member.getMemberId());
138                            break;
139                        }
140                        if (newId != null) {
141                            newMembers.add(new RelationMemberData(member.getRole(), memberType, newId));
142                        }
143                    }
144                    ((RelationData)data).setMembers(newMembers);
145                }
146            }
147    
148            /* Now execute the commands to add the duplicated contents of the paste buffer to the map */
149    
150            Main.main.undoRedo.add(new AddPrimitivesCommand(bufferCopy));
151            Main.map.mapView.repaint();
152        }
153    
154        protected boolean confirmDeleteIncomplete() {
155            ExtendedDialog ed = new ExtendedDialog(Main.parent,
156                    tr("Delete incomplete members?"),
157                    new String[] {tr("Paste without incomplete members"), tr("Cancel")});
158            ed.setButtonIcons(new String[] {"dialogs/relation/deletemembers.png", "cancel.png"});
159            ed.setContent(tr("The copied data contains incomplete objects.  "
160                    + "When pasting the incomplete objects are removed.  "
161                    + "Do you want to paste the data without the incomplete objects?"));
162            ed.showDialog();
163            return ed.getValue() == 1;
164        }
165    
166        @Override
167        protected void updateEnabledState() {
168            if (getCurrentDataSet() == null || Main.pasteBuffer == null) {
169                setEnabled(false);
170                return;
171            }
172            setEnabled(!Main.pasteBuffer.isEmpty());
173        }
174    
175        @Override
176        public void pasteBufferChanged(PrimitiveDeepCopy pasteBuffer) {
177            updateEnabledState();
178        }
179    }