Ticket #12478: patch-patch-ccp-primitives-copy-no-layers.patch

File patch-patch-ccp-primitives-copy-no-layers.patch, 162.3 KB (added by michael2402, 8 years ago)
  • src/org/openstreetmap/josm/Main.java

    diff --git a/src/org/openstreetmap/josm/Main.java b/src/org/openstreetmap/josm/Main.java
    index 09e5a10..93ace13 100644
    a b import org.openstreetmap.josm.gui.MainMenu;  
    8383import org.openstreetmap.josm.gui.MainPanel;
    8484import org.openstreetmap.josm.gui.MapFrame;
    8585import org.openstreetmap.josm.gui.MapFrameListener;
     86import org.openstreetmap.josm.gui.datatransfer.OsmTransferHandler;
    8687import org.openstreetmap.josm.gui.help.HelpUtil;
    8788import org.openstreetmap.josm.gui.io.SaveLayersDialog;
    8889import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
    public abstract class Main {  
    177178
    178179    /**
    179180     * The global paste buffer.
     181     * @deprecated Use swing CCP instead. See {@link OsmTransferHandler}
    180182     */
     183    @Deprecated
    181184    public static final PrimitiveDeepCopy pasteBuffer = new PrimitiveDeepCopy();
    182185
    183186    /**
    184187     * The layer source from which {@link Main#pasteBuffer} data comes from.
     188     * @deprecated During a copy operation, the layer should be added. See {@link OsmLayerTransferData}.
    185189     */
     190    @Deprecated
    186191    public static Layer pasteSource;
    187192
    188193    /**
  • src/org/openstreetmap/josm/actions/CopyAction.java

    diff --git a/src/org/openstreetmap/josm/actions/CopyAction.java b/src/org/openstreetmap/josm/actions/CopyAction.java
    index 6db64bf..e509506 100644
    a b import static org.openstreetmap.josm.tools.I18n.tr;  
    88import java.awt.event.ActionEvent;
    99import java.awt.event.KeyEvent;
    1010import java.util.Collection;
     11import java.util.Collections;
    1112
    1213import javax.swing.JOptionPane;
    1314
    1415import org.openstreetmap.josm.Main;
     16import org.openstreetmap.josm.data.osm.DataSet;
    1517import org.openstreetmap.josm.data.osm.OsmPrimitive;
    16 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
     18import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
     19import org.openstreetmap.josm.gui.datatransfer.PrimitiveTransferable;
     20import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData;
    1721import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    1822import org.openstreetmap.josm.tools.Shortcut;
    19 import org.openstreetmap.josm.tools.Utils;
    2023
    2124/**
    2225 * Copy OSM primitives to clipboard in order to paste them, or their tags, somewhere else.
    2326 * @since 404
    2427 */
    25 public final class CopyAction extends JosmAction {
    26 
    27     /** regular expression that matches text clipboard contents after copying */
    28     public static final String CLIPBOARD_REGEXP = "((node|way|relation)\\s\\d+,)*(node|way|relation)\\s\\d+";
    29 
     28public class CopyAction extends JosmAction {
    3029    /**
    3130     * Constructs a new {@code CopyAction}.
    3231     */
    public final class CopyAction extends JosmAction {  
    4241
    4342    @Override
    4443    public void actionPerformed(ActionEvent e) {
    45         if (isEmptySelection()) return;
    46         Collection<OsmPrimitive> selection = getLayerManager().getEditDataSet().getSelected();
     44        DataSet set = getLayerManager().getEditDataSet();
     45        Collection<OsmPrimitive> selection = set == null ? Collections.<OsmPrimitive>emptySet() : set.getSelected();
     46        if (selection.isEmpty()) {
     47            showEmptySelectionWarning();
     48            return;
     49        }
    4750
    4851        copy(getLayerManager().getEditLayer(), selection);
    4952    }
    public final class CopyAction extends JosmAction {  
    5659     */
    5760    public static void copy(OsmDataLayer source, Collection<OsmPrimitive> primitives) {
    5861        // copy ids to the clipboard
    59         String ids = getCopyString(primitives);
    60         Utils.copyToClipboard(ids);
    61 
    62         Main.pasteBuffer.makeCopy(primitives);
    63         Main.pasteSource = source;
    64     }
    65 
    66     static String getCopyString(Collection<? extends OsmPrimitive> primitives) {
    67         StringBuilder idsBuilder = new StringBuilder();
    68         for (OsmPrimitive p : primitives) {
    69             idsBuilder.append(OsmPrimitiveType.from(p).getAPIName()).append(' ').append(p.getId()).append(',');
    70         }
    71         return idsBuilder.substring(0, idsBuilder.length() - 1);
     62        PrimitiveTransferData data = PrimitiveTransferData.getDataWithReferences(primitives);
     63        ClipboardUtils.copy(new PrimitiveTransferable(data, source));
    7264    }
    7365
    7466    @Override
    public final class CopyAction extends JosmAction {  
    8173        setEnabled(selection != null && !selection.isEmpty());
    8274    }
    8375
    84     private boolean isEmptySelection() {
    85         Collection<OsmPrimitive> sel = getLayerManager().getEditDataSet().getSelected();
    86         if (sel.isEmpty()) {
    87             JOptionPane.showMessageDialog(
    88                     Main.parent,
    89                     tr("Please select something to copy."),
    90                     tr("Information"),
    91                     JOptionPane.INFORMATION_MESSAGE
    92             );
    93             return true;
    94         }
    95         return false;
     76    protected void showEmptySelectionWarning() {
     77        JOptionPane.showMessageDialog(
     78                Main.parent,
     79                tr("Please select something to copy."),
     80                tr("Information"),
     81                JOptionPane.INFORMATION_MESSAGE
     82        );
    9683    }
    9784}
  • src/org/openstreetmap/josm/actions/CopyCoordinatesAction.java

    diff --git a/src/org/openstreetmap/josm/actions/CopyCoordinatesAction.java b/src/org/openstreetmap/josm/actions/CopyCoordinatesAction.java
    index 2394349..f13f2db 100644
    a b import java.util.Collections;  
    1111import org.openstreetmap.josm.data.osm.DataSet;
    1212import org.openstreetmap.josm.data.osm.Node;
    1313import org.openstreetmap.josm.data.osm.OsmPrimitive;
     14import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
    1415import org.openstreetmap.josm.tools.Shortcut;
    1516import org.openstreetmap.josm.tools.Utils;
    1617
    public class CopyCoordinatesAction extends JosmAction {  
    3435            s.append(n.getCoor().lon());
    3536            s.append('\n');
    3637        }
    37         Utils.copyToClipboard(s.toString().trim());
     38        ClipboardUtils.copyString(s.toString().trim());
    3839    }
    3940
    4041    @Override
  • src/org/openstreetmap/josm/actions/DuplicateAction.java

    diff --git a/src/org/openstreetmap/josm/actions/DuplicateAction.java b/src/org/openstreetmap/josm/actions/DuplicateAction.java
    index 488b119..8467c3e 100644
    a b import java.util.Collection;  
    1111
    1212import org.openstreetmap.josm.Main;
    1313import org.openstreetmap.josm.data.osm.OsmPrimitive;
    14 import org.openstreetmap.josm.data.osm.PrimitiveDeepCopy;
     14import org.openstreetmap.josm.gui.datatransfer.OsmTransferHandler;
     15import org.openstreetmap.josm.gui.datatransfer.PrimitiveTransferable;
     16import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData;
    1517import org.openstreetmap.josm.tools.Shortcut;
    1618
     19/**
     20 * An action that dupplicates the given nodes. They are not added to the clippboard.
     21 */
    1722public final class DuplicateAction extends JosmAction {
    1823
    1924    /**
    public final class DuplicateAction extends JosmAction {  
    2126     */
    2227    public DuplicateAction() {
    2328        super(tr("Duplicate"), "duplicate",
    24                 tr("Duplicate selection by copy and immediate paste."),
     29                tr("Duplicate selection."),
    2530                Shortcut.registerShortcut("system:duplicate", tr("Edit: {0}", tr("Duplicate")), KeyEvent.VK_D, Shortcut.CTRL), true);
    2631        putValue("help", ht("/Action/Duplicate"));
    2732    }
    2833
    2934    @Override
    3035    public void actionPerformed(ActionEvent e) {
    31         Main.main.menu.paste.pasteData(
    32                 new PrimitiveDeepCopy(getLayerManager().getEditDataSet().getSelected()), getLayerManager().getEditLayer(), e);
     36        PrimitiveTransferData data = PrimitiveTransferData.getDataWithReferences(getLayerManager().getEditDataSet().getSelected());
     37        new OsmTransferHandler().pasteOn(Main.getLayerManager().getEditLayer(), data.getCenter(), new PrimitiveTransferable(data));
    3338    }
    3439
    3540    @Override
  • src/org/openstreetmap/josm/actions/MapRectifierWMSmenuAction.java

    diff --git a/src/org/openstreetmap/josm/actions/MapRectifierWMSmenuAction.java b/src/org/openstreetmap/josm/actions/MapRectifierWMSmenuAction.java
    index 12f5bdb..6fb6424 100644
    a b import javax.swing.JRadioButton;  
    2222import org.openstreetmap.josm.Main;
    2323import org.openstreetmap.josm.data.imagery.ImageryInfo;
    2424import org.openstreetmap.josm.gui.ExtendedDialog;
     25import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
    2526import org.openstreetmap.josm.gui.layer.WMSLayer;
    2627import org.openstreetmap.josm.gui.widgets.JosmTextField;
    2728import org.openstreetmap.josm.gui.widgets.UrlLabel;
    2829import org.openstreetmap.josm.tools.GBC;
    2930import org.openstreetmap.josm.tools.Shortcut;
    30 import org.openstreetmap.josm.tools.Utils;
    3131
    3232/**
    3333 * Download rectified images from various services.
    public class MapRectifierWMSmenuAction extends JosmAction {  
    123123
    124124        JosmTextField tfWmsUrl = new JosmTextField(30);
    125125
    126         String clip = Utils.getClipboardContent();
     126        String clip = ClipboardUtils.getClipboardStringContent();
    127127        clip = clip == null ? "" : clip.trim();
    128128        ButtonGroup group = new ButtonGroup();
    129129
  • src/org/openstreetmap/josm/actions/PasteAction.java

    diff --git a/src/org/openstreetmap/josm/actions/PasteAction.java b/src/org/openstreetmap/josm/actions/PasteAction.java
    index d0a1b4b..19f076b 100644
    a b import static org.openstreetmap.josm.tools.I18n.tr;  
    77
    88import java.awt.MouseInfo;
    99import java.awt.Point;
     10import java.awt.datatransfer.FlavorEvent;
     11import java.awt.datatransfer.FlavorListener;
    1012import java.awt.event.ActionEvent;
    1113import java.awt.event.KeyEvent;
    12 import java.util.ArrayList;
    13 import java.util.HashMap;
    14 import java.util.List;
    15 import java.util.Map;
    1614
    1715import org.openstreetmap.josm.Main;
    18 import org.openstreetmap.josm.command.AddPrimitivesCommand;
    1916import org.openstreetmap.josm.data.coor.EastNorth;
    20 import org.openstreetmap.josm.data.osm.NodeData;
    21 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
    22 import org.openstreetmap.josm.data.osm.PrimitiveData;
    23 import org.openstreetmap.josm.data.osm.PrimitiveDeepCopy;
    24 import org.openstreetmap.josm.data.osm.PrimitiveDeepCopy.PasteBufferChangedListener;
    25 import org.openstreetmap.josm.data.osm.RelationData;
    26 import org.openstreetmap.josm.data.osm.RelationMemberData;
    27 import org.openstreetmap.josm.data.osm.WayData;
    28 import org.openstreetmap.josm.gui.ExtendedDialog;
    29 import org.openstreetmap.josm.gui.layer.Layer;
     17import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
     18import org.openstreetmap.josm.gui.datatransfer.OsmTransferHandler;
    3019import org.openstreetmap.josm.tools.Shortcut;
    3120
    3221/**
    3322 * Paste OSM primitives from clipboard to the current edit layer.
    3423 * @since 404
    3524 */
    36 public final class PasteAction extends JosmAction implements PasteBufferChangedListener {
     25public final class PasteAction extends JosmAction implements FlavorListener {
     26
     27    private final OsmTransferHandler transferHandler;
    3728
    3829    /**
    3930     * Constructs a new {@code PasteAction}.
    public final class PasteAction extends JosmAction implements PasteBufferChangedL  
    4536        // CUA shortcut for paste (https://en.wikipedia.org/wiki/IBM_Common_User_Access#Description)
    4637        Main.registerActionShortcut(this,
    4738                Shortcut.registerShortcut("system:paste:cua", tr("Edit: {0}", tr("Paste")), KeyEvent.VK_INSERT, Shortcut.SHIFT));
    48         Main.pasteBuffer.addPasteBufferChangedListener(this);
     39        transferHandler = new OsmTransferHandler();
     40        ClipboardUtils.getClipboard().addFlavorListener(this);
    4941    }
    5042
    5143    @Override
    5244    public void actionPerformed(ActionEvent e) {
    53         if (!isEnabled())
    54             return;
    55         pasteData(Main.pasteBuffer, Main.pasteSource, e);
    56     }
    57 
    58     /**
    59      * Paste OSM primitives from the given paste buffer and OSM data layer source to the current edit layer.
    60      * @param pasteBuffer The paste buffer containing primitive ids to copy
    61      * @param source The OSM data layer used to look for primitive ids
    62      * @param e The ActionEvent that triggered this operation
    63      */
    64     public void pasteData(PrimitiveDeepCopy pasteBuffer, Layer source, ActionEvent e) {
    65         /* Find the middle of the pasteBuffer area */
    66         double maxEast = -1E100;
    67         double minEast = 1E100;
    68         double maxNorth = -1E100;
    69         double minNorth = 1E100;
    70         boolean incomplete = false;
    71         for (PrimitiveData data : pasteBuffer.getAll()) {
    72             if (data instanceof NodeData) {
    73                 NodeData n = (NodeData) data;
    74                 if (n.getEastNorth() != null) {
    75                     double east = n.getEastNorth().east();
    76                     double north = n.getEastNorth().north();
    77                     if (east > maxEast) {
    78                         maxEast = east;
    79                     }
    80                     if (east < minEast) {
    81                         minEast = east;
    82                     }
    83                     if (north > maxNorth) {
    84                         maxNorth = north;
    85                     }
    86                     if (north < minNorth) {
    87                         minNorth = north;
    88                     }
    89                 }
    90             }
    91             if (data.isIncomplete()) {
    92                 incomplete = true;
    93             }
    94         }
    95 
    96         // Allow to cancel paste if there are incomplete primitives
    97         if (incomplete && !confirmDeleteIncomplete()) {
    98             return;
    99         }
    100 
    10145        // default to paste in center of map (pasted via menu or cursor not in MapView)
    10246        EastNorth mPosition = Main.map.mapView.getCenter();
    10347        // We previously checked for modifier to know if the action has been trigerred via shortcut or via menu
    public final class PasteAction extends JosmAction implements PasteBufferChangedL  
    11256            }
    11357        }
    11458
    115         double offsetEast = mPosition.east() - (maxEast + minEast)/2.0;
    116         double offsetNorth = mPosition.north() - (maxNorth + minNorth)/2.0;
    117 
    118         // Make a copy of pasteBuffer and map from old id to copied data id
    119         List<PrimitiveData> bufferCopy = new ArrayList<>();
    120         List<PrimitiveData> toSelect = new ArrayList<>();
    121         Map<Long, Long> newNodeIds = new HashMap<>();
    122         Map<Long, Long> newWayIds = new HashMap<>();
    123         Map<Long, Long> newRelationIds = new HashMap<>();
    124         for (PrimitiveData data: pasteBuffer.getAll()) {
    125             if (data.isIncomplete()) {
    126                 continue;
    127             }
    128             PrimitiveData copy = data.makeCopy();
    129             copy.clearOsmMetadata();
    130             if (data instanceof NodeData) {
    131                 newNodeIds.put(data.getUniqueId(), copy.getUniqueId());
    132             } else if (data instanceof WayData) {
    133                 newWayIds.put(data.getUniqueId(), copy.getUniqueId());
    134             } else if (data instanceof RelationData) {
    135                 newRelationIds.put(data.getUniqueId(), copy.getUniqueId());
    136             }
    137             bufferCopy.add(copy);
    138             if (pasteBuffer.getDirectlyAdded().contains(data)) {
    139                 toSelect.add(copy);
    140             }
    141         }
    142 
    143         // Update references in copied buffer
    144         for (PrimitiveData data:bufferCopy) {
    145             if (data instanceof NodeData) {
    146                 NodeData nodeData = (NodeData) data;
    147                 if (Main.getLayerManager().getEditLayer() == source) {
    148                     nodeData.setEastNorth(nodeData.getEastNorth().add(offsetEast, offsetNorth));
    149                 }
    150             } else if (data instanceof WayData) {
    151                 List<Long> newNodes = new ArrayList<>();
    152                 for (Long oldNodeId: ((WayData) data).getNodes()) {
    153                     Long newNodeId = newNodeIds.get(oldNodeId);
    154                     if (newNodeId != null) {
    155                         newNodes.add(newNodeId);
    156                     }
    157                 }
    158                 ((WayData) data).setNodes(newNodes);
    159             } else if (data instanceof RelationData) {
    160                 List<RelationMemberData> newMembers = new ArrayList<>();
    161                 for (RelationMemberData member: ((RelationData) data).getMembers()) {
    162                     OsmPrimitiveType memberType = member.getMemberType();
    163                     Long newId;
    164                     switch (memberType) {
    165                     case NODE:
    166                         newId = newNodeIds.get(member.getMemberId());
    167                         break;
    168                     case WAY:
    169                         newId = newWayIds.get(member.getMemberId());
    170                         break;
    171                     case RELATION:
    172                         newId = newRelationIds.get(member.getMemberId());
    173                         break;
    174                     default: throw new AssertionError();
    175                     }
    176                     if (newId != null) {
    177                         newMembers.add(new RelationMemberData(member.getRole(), memberType, newId));
    178                     }
    179                 }
    180                 ((RelationData) data).setMembers(newMembers);
    181             }
    182         }
    183 
    184         /* Now execute the commands to add the duplicated contents of the paste buffer to the map */
    185         Main.main.undoRedo.add(new AddPrimitivesCommand(bufferCopy, toSelect));
    186         Main.map.mapView.repaint();
    187     }
    188 
    189     private static boolean confirmDeleteIncomplete() {
    190         ExtendedDialog ed = new ExtendedDialog(Main.parent,
    191                 tr("Delete incomplete members?"),
    192                 new String[] {tr("Paste without incomplete members"), tr("Cancel")});
    193         ed.setButtonIcons(new String[] {"dialogs/relation/deletemembers", "cancel"});
    194         ed.setContent(tr("The copied data contains incomplete objects.  "
    195                 + "When pasting the incomplete objects are removed.  "
    196                 + "Do you want to paste the data without the incomplete objects?"));
    197         ed.showDialog();
    198         return ed.getValue() == 1;
     59        transferHandler.pasteOn(Main.getLayerManager().getEditLayer(), mPosition);
    19960    }
    20061
    20162    @Override
    20263    protected void updateEnabledState() {
    203         if (getLayerManager().getEditDataSet() == null || Main.pasteBuffer == null) {
    204             setEnabled(false);
    205             return;
    206         }
    207         setEnabled(!Main.pasteBuffer.isEmpty());
     64        setEnabled(getLayerManager().getEditDataSet() != null && transferHandler.isDataAvailable());
    20865    }
    20966
    21067    @Override
    211     public void pasteBufferChanged(PrimitiveDeepCopy pasteBuffer) {
     68    public void flavorsChanged(FlavorEvent e) {
    21269        updateEnabledState();
    21370    }
    21471}
  • src/org/openstreetmap/josm/actions/PasteTagsAction.java

    diff --git a/src/org/openstreetmap/josm/actions/PasteTagsAction.java b/src/org/openstreetmap/josm/actions/PasteTagsAction.java
    index 18791ce..027de5d 100644
    a b import org.openstreetmap.josm.data.osm.PrimitiveData;  
    2525import org.openstreetmap.josm.data.osm.Tag;
    2626import org.openstreetmap.josm.data.osm.TagCollection;
    2727import org.openstreetmap.josm.gui.conflict.tags.PasteTagsConflictResolverDialog;
     28import org.openstreetmap.josm.gui.datatransfer.OsmTransferHandler;
    2829import org.openstreetmap.josm.tools.I18n;
    2930import org.openstreetmap.josm.tools.Shortcut;
    3031import org.openstreetmap.josm.tools.TextTagParser;
    31 import org.openstreetmap.josm.tools.Utils;
    3232
    3333/**
    3434 * Action, to paste all tags from one primitive to another.
    import org.openstreetmap.josm.tools.Utils;  
    4141public final class PasteTagsAction extends JosmAction {
    4242
    4343    private static final String help = ht("/Action/PasteTags");
     44    private final OsmTransferHandler transferHandler = new OsmTransferHandler();
    4445
    4546    /**
    4647     * Constructs a new {@code PasteTagsAction}.
    public final class PasteTagsAction extends JosmAction {  
    5354        putValue("help", help);
    5455    }
    5556
     57    /**
     58     * Used to update the tags.
     59     */
    5660    public static class TagPaster {
    5761
    5862        private final Collection<PrimitiveData> source;
    public final class PasteTagsAction extends JosmAction {  
    258262        if (selection.isEmpty())
    259263            return;
    260264
    261         String buf = Utils.getClipboardContent();
    262         if (buf == null || buf.isEmpty() || buf.matches(CopyAction.CLIPBOARD_REGEXP)) {
    263             pasteTagsFromJOSMBuffer(selection);
    264         } else {
    265             // Paste tags from arbitrary text
    266             pasteTagsFromText(selection, buf);
    267         }
     265        transferHandler.pasteTags(selection);
    268266    }
    269267
    270268    /**
    public final class PasteTagsAction extends JosmAction {  
    292290    }
    293291
    294292    /**
    295      * Paste tags from JOSM buffer
    296      * @param selection objects that will have the tags
    297      * @return false if JOSM buffer was empty
    298      */
    299     public static boolean pasteTagsFromJOSMBuffer(Collection<OsmPrimitive> selection) {
    300         List<PrimitiveData> directlyAdded = Main.pasteBuffer.getDirectlyAdded();
    301         if (directlyAdded == null || directlyAdded.isEmpty()) return false;
    302 
    303         PasteTagsAction.TagPaster tagPaster = new PasteTagsAction.TagPaster(directlyAdded, selection);
    304         List<Command> commands = new ArrayList<>();
    305         for (Tag tag : tagPaster.execute()) {
    306             commands.add(new ChangePropertyCommand(selection, tag.getKey(), "".equals(tag.getValue()) ? null : tag.getValue()));
    307         }
    308         commitCommands(selection, commands);
    309         return true;
    310     }
    311 
    312     /**
    313293     * Create and execute SequenceCommand with descriptive title
    314294     * @param selection selected primitives
    315295     * @param commands the commands to perform in a sequential command
  • src/org/openstreetmap/josm/data/osm/PrimitiveData.java

    diff --git a/src/org/openstreetmap/josm/data/osm/PrimitiveData.java b/src/org/openstreetmap/josm/data/osm/PrimitiveData.java
    index 5664dd3..693d277 100644
    a b public abstract class PrimitiveData extends AbstractPrimitive implements Seriali  
    8080        oos.writeInt(version);
    8181        oos.writeInt(changesetId);
    8282        oos.writeInt(timestamp);
     83        oos.writeObject(keys);
    8384        oos.defaultWriteObject();
    8485    }
    8586
    public abstract class PrimitiveData extends AbstractPrimitive implements Seriali  
    9192        version = ois.readInt();
    9293        changesetId = ois.readInt();
    9394        timestamp = ois.readInt();
     95        keys = (String[]) ois.readObject();
    9496        ois.defaultReadObject();
    9597    }
    9698}
  • src/org/openstreetmap/josm/data/osm/PrimitiveDeepCopy.java

    diff --git a/src/org/openstreetmap/josm/data/osm/PrimitiveDeepCopy.java b/src/org/openstreetmap/josm/data/osm/PrimitiveDeepCopy.java
    index 03db280..3fc21ea 100644
    a b  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data.osm;
    33
     4import java.awt.datatransfer.UnsupportedFlavorException;
     5import java.io.IOException;
    46import java.util.ArrayList;
    57import java.util.Collection;
    6 import java.util.HashSet;
     8import java.util.Collections;
    79import java.util.List;
    8 import java.util.Set;
    9 import java.util.concurrent.CopyOnWriteArrayList;
    1010
    11 import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
     11import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
     12import org.openstreetmap.josm.gui.datatransfer.OsmTransferHandler;
     13import org.openstreetmap.josm.gui.datatransfer.PrimitiveTransferable;
     14import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData;
    1215
    1316/**
    1417 * This class allows to create and keep a deep copy of primitives. Provides methods to access directly added
    1518 * primitives and reference primitives
     19 * <p>
     20 * To be removed end of 2016
    1621 * @since 2305
     22 * @deprecated This has been replaced by Swing Copy+Paste support. Use {@link OsmTransferHandler} instead.
    1723 */
     24@Deprecated
    1825public class PrimitiveDeepCopy {
    1926
    2027    public interface PasteBufferChangedListener {
    2128        void pasteBufferChanged(PrimitiveDeepCopy pasteBuffer);
    2229    }
    2330
    24     private final List<PrimitiveData> directlyAdded = new ArrayList<>();
    25     private final List<PrimitiveData> referenced = new ArrayList<>();
    26     private final CopyOnWriteArrayList<PasteBufferChangedListener> listeners = new CopyOnWriteArrayList<>();
    27 
    2831    /**
    2932     * Constructs a new {@code PrimitiveDeepCopy} without data. Use {@link #makeCopy(Collection)} after that.
    3033     */
    3134    public PrimitiveDeepCopy() {
    32         // Do nothing
    3335    }
    3436
    3537    /**
    public class PrimitiveDeepCopy {  
    4547     * Replace content of the object with copy of provided primitives.
    4648     * @param primitives OSM primitives to copy
    4749     * @since 7961
     50     * @deprecated Call {@link OsmTransferHandler#copyToClippboard(PrimitiveTransferData)} yourself
    4851     */
     52    @Deprecated
    4953    public final void makeCopy(final Collection<? extends OsmPrimitive> primitives) {
    50         directlyAdded.clear();
    51         referenced.clear();
    52 
    53         final Set<Long> visitedNodeIds = new HashSet<>();
    54         final Set<Long> visitedWayIds = new HashSet<>();
    55         final Set<Long> visitedRelationIds = new HashSet<>();
    56 
    57         new AbstractVisitor() {
    58             private boolean firstIteration;
    59 
    60             @Override
    61             public void visit(Node n) {
    62                 if (!visitedNodeIds.add(n.getUniqueId()))
    63                     return;
    64                 (firstIteration ? directlyAdded : referenced).add(n.save());
    65             }
    66 
    67             @Override
    68             public void visit(Way w) {
    69                 if (!visitedWayIds.add(w.getUniqueId()))
    70                     return;
    71                 (firstIteration ? directlyAdded : referenced).add(w.save());
    72                 firstIteration = false;
    73                 for (Node n : w.getNodes()) {
    74                     visit(n);
    75                 }
    76             }
    77 
    78             @Override
    79             public void visit(Relation r) {
    80                 if (!visitedRelationIds.add(r.getUniqueId()))
    81                     return;
    82                 (firstIteration ? directlyAdded : referenced).add(r.save());
    83                 firstIteration = false;
    84                 for (RelationMember m : r.getMembers()) {
    85                     m.getMember().accept(this);
    86                 }
    87             }
    88 
    89             public void visitAll() {
    90                 for (OsmPrimitive osm : primitives) {
    91                     firstIteration = true;
    92                     osm.accept(this);
    93                 }
    94             }
    95         }.visitAll();
    96 
    97         firePasteBufferChanged();
     54        ClipboardUtils.copy(new PrimitiveTransferable(PrimitiveTransferData.getDataWithReferences(primitives)));
    9855    }
    9956
     57    /**
     58     * Gets the list of primitives that were explicitly added to this copy.
     59     * @return The added primitives
     60     */
    10061    public List<PrimitiveData> getDirectlyAdded() {
    101         return directlyAdded;
     62        try {
     63            PrimitiveTransferData data = (PrimitiveTransferData) ClipboardUtils.getClipboard().getData(PrimitiveTransferData.DATA_FLAVOR);
     64            return new ArrayList<>(data.getDirectlyAdded());
     65        } catch (UnsupportedFlavorException | IOException e) {
     66            return Collections.emptyList();
     67        }
    10268    }
    10369
     70    /**
     71     * Gets the list of primitives that were implicitly added because they were referenced.
     72     * @return The primitives
     73     */
    10474    public List<PrimitiveData> getReferenced() {
    105         return referenced;
     75        try {
     76            PrimitiveTransferData data = (PrimitiveTransferData) ClipboardUtils.getClipboard().getData(PrimitiveTransferData.DATA_FLAVOR);
     77            return new ArrayList<>(data.getReferenced());
     78        } catch (UnsupportedFlavorException | IOException e) {
     79            return Collections.emptyList();
     80        }
    10681    }
    10782
     83    /**
     84     * Gets a list of all primitives in this copy.
     85     * @return The primitives
     86     * @see #getDirectlyAdded()
     87     * @see #getReferenced()
     88     */
    10889    public List<PrimitiveData> getAll() {
    109         List<PrimitiveData> result = new ArrayList<>(directlyAdded.size() + referenced.size());
    110         result.addAll(directlyAdded);
    111         result.addAll(referenced);
    112         return result;
     90        try {
     91            PrimitiveTransferData data = (PrimitiveTransferData) ClipboardUtils.getClipboard().getData(PrimitiveTransferData.DATA_FLAVOR);
     92            return new ArrayList<>(data.getAll());
     93        } catch (UnsupportedFlavorException | IOException e) {
     94            return Collections.emptyList();
     95        }
    11396    }
    11497
    11598    public boolean isEmpty() {
    116         return directlyAdded.isEmpty() && referenced.isEmpty();
    117     }
    118 
    119     private void firePasteBufferChanged() {
    120         for (PasteBufferChangedListener listener: listeners) {
    121             listener.pasteBufferChanged(this);
    122         }
     99        return !ClipboardUtils.getClipboard().isDataFlavorAvailable(PrimitiveTransferData.DATA_FLAVOR);
    123100    }
    124101
     102    /**
     103     * Deactivated. To be removed as soon as we think nobody uses it.
     104     * @param listener
     105     * @deprecated You can detect buffer changes by registering a listener on {@link OsmTransferHandler#getClipboard()}
     106     */
     107    @Deprecated
    125108    public void addPasteBufferChangedListener(PasteBufferChangedListener listener) {
    126         listeners.addIfAbsent(listener);
    127109    }
    128110
     111    @Deprecated
    129112    public void removePasteBufferChangedListener(PasteBufferChangedListener listener) {
    130         listeners.remove(listener);
    131113    }
    132114}
  • src/org/openstreetmap/josm/data/osm/TagMap.java

    diff --git a/src/org/openstreetmap/josm/data/osm/TagMap.java b/src/org/openstreetmap/josm/data/osm/TagMap.java
    index ce49e40..7f413ca 100644
    a b  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data.osm;
    33
     4import java.io.Serializable;
    45import java.util.AbstractMap;
    56import java.util.AbstractSet;
     7import java.util.ArrayList;
    68import java.util.Arrays;
    79import java.util.ConcurrentModificationException;
    810import java.util.Iterator;
     11import java.util.List;
     12import java.util.Map;
    913import java.util.NoSuchElementException;
    1014import java.util.Set;
    1115
    import java.util.Set;  
    1620 *
    1721 * @author Michael Zangl
    1822 */
    19 public class TagMap extends AbstractMap<String, String> {
     23public class TagMap extends AbstractMap<String, String> implements Serializable {
     24    static final long serialVersionUID = 1;
    2025    /**
    2126     * We use this array every time we want to represent an empty map.
    2227     * This saves us the burden of checking for null every time but saves some object allocations.
    public class TagMap extends AbstractMap<String, String> {  
    107112     * Creates a new, empty tag map.
    108113     */
    109114    public TagMap() {
    110         this(null);
     115        this((String[]) null);
     116    }
     117
     118    /**
     119     * Create a new tag map and load it from the other map.
     120     * @param tags The map to load from.
     121     */
     122    public TagMap(Map<String, String> tags) {
     123        putAll(tags);
     124    }
     125
     126    /**
     127     * Copy constructor
     128     * @param tagMap The map to copy from.
     129     */
     130    public TagMap(TagMap tagMap) {
     131        this(tagMap.tags);
    111132    }
    112133
    113134    /**
    public class TagMap extends AbstractMap<String, String> {  
    209230    }
    210231
    211232    /**
     233     * Gets a list of all tags contained in this map.
     234     * @return The list of tags in the order they were added.
     235     */
     236    public List<Tag> getTags() {
     237        String[] tags = this.tags;
     238        List<Tag> tagList = new ArrayList<>();
     239        for (int i = 0; i < tags.length; i+=2) {
     240            tagList.add(new Tag(tags[i], tags[i+1]));
     241        }
     242        return tagList;
     243    }
     244
     245    /**
    212246     * Finds a key in an array that is structured like the {@link #tags} array and returns the position.
    213247     * <p>
    214248     * We allow the parameter to be passed to allow for better synchronization.
  • src/org/openstreetmap/josm/gui/MapFrame.java

    diff --git a/src/org/openstreetmap/josm/gui/MapFrame.java b/src/org/openstreetmap/josm/gui/MapFrame.java
    index e3ab5cc..80ce939 100644
    a b import java.awt.Component;  
    88import java.awt.Container;
    99import java.awt.Dimension;
    1010import java.awt.Font;
    11 import java.awt.GraphicsEnvironment;
    1211import java.awt.GridBagLayout;
    1312import java.awt.Rectangle;
    1413import java.awt.event.ActionEvent;
    public class MapFrame extends JPanel implements Destroyable, ActiveLayerChangeLi  
    196195        setLayout(new BorderLayout());
    197196
    198197        mapView = new MapView(Main.getLayerManager(), contentPane, viewportData);
    199         if (!GraphicsEnvironment.isHeadless()) {
    200             new FileDrop(mapView);
    201         }
    202198
    203199        splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true);
    204200
  • src/org/openstreetmap/josm/gui/MapView.java

    diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java
    index da3079e..c20ee16 100644
    a b import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;  
    5252import org.openstreetmap.josm.data.osm.visitor.paint.Rendering;
    5353import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
    5454import org.openstreetmap.josm.gui.MapViewState.MapViewRectangle;
     55import org.openstreetmap.josm.gui.datatransfer.OsmTransferHandler;
    5556import org.openstreetmap.josm.gui.layer.AbstractMapViewPaintable;
    5657import org.openstreetmap.josm.gui.layer.GpxLayer;
    5758import org.openstreetmap.josm.gui.layer.ImageryLayer;
    LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener {  
    579580        for (JComponent c : getMapNavigationComponents(MapView.this)) {
    580581            add(c);
    581582        }
     583        setTransferHandler(new OsmTransferHandler());
    582584    }
    583585
    584586    /**
  • new file src/org/openstreetmap/josm/gui/datatransfer/ClipboardUtils.java

    diff --git a/src/org/openstreetmap/josm/gui/datatransfer/ClipboardUtils.java b/src/org/openstreetmap/josm/gui/datatransfer/ClipboardUtils.java
    new file mode 100644
    index 0000000..f320a99
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.datatransfer;
     3
     4import java.awt.GraphicsEnvironment;
     5import java.awt.HeadlessException;
     6import java.awt.Toolkit;
     7import java.awt.datatransfer.Clipboard;
     8import java.awt.datatransfer.ClipboardOwner;
     9import java.awt.datatransfer.DataFlavor;
     10import java.awt.datatransfer.StringSelection;
     11import java.awt.datatransfer.Transferable;
     12import java.awt.datatransfer.UnsupportedFlavorException;
     13import java.io.IOException;
     14import java.util.concurrent.Callable;
     15
     16import org.openstreetmap.josm.Main;
     17import org.openstreetmap.josm.gui.util.GuiHelper;
     18import org.openstreetmap.josm.tools.Utils;
     19
     20/**
     21 * This is a utility class that provides methods useful for general data transfer support.
     22 *
     23 * @author Michael Zangl
     24 * @since xxx
     25 */
     26public final class ClipboardUtils {
     27    private static final class DoNothingClipboardOwner implements ClipboardOwner {
     28        @Override
     29        public void lostOwnership(Clipboard clpbrd, Transferable t) {
     30            // Do nothing
     31        }
     32    }
     33
     34    private static Clipboard clipboard;
     35
     36    private ClipboardUtils() {
     37    }
     38
     39    /**
     40     * This method should be used from all of JOSM to access the clipboard.
     41     * <p>
     42     * It will default to the system clipboard except for cases where that clipboard is not accessible.
     43     * @return A clipboard.
     44     * @see #getClipboardContent()
     45     */
     46    public static synchronized Clipboard getClipboard() {
     47        // Might be unsupported in some more cases, we need a fake clipboard then.
     48        if (clipboard == null) {
     49            try {
     50                clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
     51            } catch (HeadlessException e) {
     52                Main.warn("Headless. Using fake clipboard.", e);
     53                clipboard = new Clipboard("fake");
     54            }
     55        }
     56        return clipboard;
     57    }
     58
     59    /**
     60     * Gets the singleton instance of the system selection as a <code>Clipboard</code> object.
     61     * This allows an application to read and modify the current, system-wide selection.
     62     * @return the system selection as a <code>Clipboard</code>, or <code>null</code> if the native platform does not
     63     *         support a system selection <code>Clipboard</code> or if GraphicsEnvironment.isHeadless() returns true
     64     * @see Toolkit#getSystemSelection
     65     * @since xxx
     66     */
     67    public static Clipboard getSystemSelection() {
     68        if (GraphicsEnvironment.isHeadless()) {
     69            return null;
     70        } else {
     71            return Toolkit.getDefaultToolkit().getSystemSelection();
     72        }
     73    }
     74
     75    /**
     76     * Gets the clipboard content as string.
     77     * @return the content if available, <code>null</code> otherwise.
     78     */
     79    public static String getClipboardStringContent() {
     80        try {
     81            Transferable t = getClipboardContent();
     82            if (t != null && t.isDataFlavorSupported(DataFlavor.stringFlavor)) {
     83                return (String) t.getTransferData(DataFlavor.stringFlavor);
     84            }
     85        } catch (UnsupportedFlavorException | IOException ex) {
     86            Main.error(ex);
     87        }
     88        return null;
     89    }
     90
     91    /**
     92     * Extracts clipboard content as {@code Transferable} object. Using this method avoids some problems on some platforms.
     93     * @return The content or <code>null</code> if it is not available
     94     */
     95    public static synchronized Transferable getClipboardContent() {
     96        return getClipboardContent(getClipboard());
     97    }
     98
     99    /**
     100     * Extracts clipboard content as {@code Transferable} object. Using this method avoids some problems on some platforms.
     101     * @param clipboard clipboard from which contents are retrieved
     102     * @return clipboard contents if available, {@code null} otherwise.
     103     * @since xx
     104     */
     105    public static Transferable getClipboardContent(Clipboard clipboard) {
     106        Transferable t = null;
     107        for (int tries = 0; t == null && tries < 10; tries++) {
     108            try {
     109                t = clipboard.getContents(null);
     110            } catch (IllegalStateException e) {
     111                // Clipboard currently unavailable.
     112                // On some platforms, the system clipboard is unavailable while it is accessed by another application.
     113                Main.trace("Clipboard unavailable.", e);
     114                try {
     115                    Thread.sleep(1);
     116                } catch (InterruptedException ex) {
     117                    Main.warn("InterruptedException in " + Utils.class.getSimpleName()
     118                            + " while getting clipboard content");
     119                }
     120            } catch (NullPointerException e) {
     121                // JDK-6322854: On Linux/X11, NPE can happen for unknown reasons, on all versions of Java
     122                Main.error(e);
     123            }
     124        }
     125        return t;
     126    }
     127
     128    /**
     129     * Copy the given string to the clipboard.
     130     * @param s The string to copy.
     131     * @return True if the  copy was successful
     132     */
     133    public static boolean copyString(String s) {
     134        return copy(new StringSelection(s));
     135    }
     136
     137    /**
     138     * Copies the given transferable to the clipboard. Handles state problems that occur on some platforms.
     139     * @param transferable The transferable to copy.
     140     * @return True if the copy was successful
     141     */
     142    public static boolean copy(final Transferable transferable) {
     143        return GuiHelper.runInEDTAndWaitAndReturn(new Callable<Boolean>() {
     144            @Override
     145            public Boolean call() throws Exception {
     146                try {
     147                    getClipboard().setContents(transferable, new DoNothingClipboardOwner());
     148                    return true;
     149                } catch (IllegalStateException ex) {
     150                    Main.error(ex);
     151                    return false;
     152                }
     153            }
     154        });
     155    }
     156}
  • new file src/org/openstreetmap/josm/gui/datatransfer/OsmTransferHandler.java

    diff --git a/src/org/openstreetmap/josm/gui/datatransfer/OsmTransferHandler.java b/src/org/openstreetmap/josm/gui/datatransfer/OsmTransferHandler.java
    new file mode 100644
    index 0000000..9f47fd2
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.datatransfer;
     3
     4import java.awt.datatransfer.DataFlavor;
     5import java.awt.datatransfer.Transferable;
     6import java.awt.datatransfer.UnsupportedFlavorException;
     7import java.io.IOException;
     8import java.util.Arrays;
     9import java.util.Collection;
     10
     11import javax.swing.TransferHandler;
     12
     13import org.openstreetmap.josm.Main;
     14import org.openstreetmap.josm.data.coor.EastNorth;
     15import org.openstreetmap.josm.data.osm.OsmPrimitive;
     16import org.openstreetmap.josm.gui.datatransfer.importers.AbstractOsmDataPaster;
     17import org.openstreetmap.josm.gui.datatransfer.importers.FilePaster;
     18import org.openstreetmap.josm.gui.datatransfer.importers.PrimitiveDataPaster;
     19import org.openstreetmap.josm.gui.datatransfer.importers.TagTransferPaster;
     20import org.openstreetmap.josm.gui.datatransfer.importers.TextTagPaster;
     21import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     22
     23/**
     24 * This transfer hanlder provides the ability to transfer OSM data. It allows you to receive files, primitives or tags.
     25 * @author Michael Zangl
     26 * @since xxx
     27 */
     28public class OsmTransferHandler extends TransferHandler {
     29
     30    private static final Collection<AbstractOsmDataPaster> SUPPORTED = Arrays.asList(
     31            new FilePaster(), new PrimitiveDataPaster(),
     32            new TagTransferPaster(), new TextTagPaster());
     33
     34    @Override
     35    public boolean canImport(TransferSupport support) {
     36        // import everything for now, only support copy.
     37        for (AbstractOsmDataPaster df : SUPPORTED) {
     38            if (df.supports(support)) {
     39                return true;
     40            }
     41        }
     42        return false;
     43    }
     44
     45    @Override
     46    public boolean importData(TransferSupport support) {
     47        return importData(support, Main.getLayerManager().getEditLayer(), null);
     48    }
     49
     50    private boolean importData(TransferSupport support, OsmDataLayer layer, EastNorth center) {
     51        for (AbstractOsmDataPaster df : SUPPORTED) {
     52            if (df.supports(support)) {
     53                try {
     54                    if (df.importData(support, layer, center)) {
     55                        return true;
     56                    }
     57                } catch (UnsupportedFlavorException | IOException e) {
     58                    Main.warn(e);
     59                }
     60            }
     61        }
     62        return super.importData(support);
     63    }
     64
     65    private boolean importTags(TransferSupport support, Collection<? extends OsmPrimitive> primitives) {
     66        for (AbstractOsmDataPaster df : SUPPORTED) {
     67            if (df.supports(support)) {
     68                try {
     69                    if (df.importTagsOn(support, primitives)) {
     70                        return true;
     71                    }
     72                } catch (UnsupportedFlavorException | IOException e) {
     73                    Main.warn(e);
     74                }
     75            }
     76        }
     77        return super.importData(support);
     78    }
     79
     80    /**
     81     * Paste the current clippboard current at the given position
     82     * @param editLayer The layer to paste on.
     83     * @param mPosition The position to paste at.
     84     */
     85    public void pasteOn(OsmDataLayer editLayer, EastNorth mPosition) {
     86        Transferable transferable = ClipboardUtils.getClipboard().getContents(null);
     87        pasteOn(editLayer, mPosition, transferable);
     88    }
     89
     90    /**
     91     * Paste the given clippboard current at the given position
     92     * @param editLayer The layer to paste on.
     93     * @param mPosition The position to paste at.
     94     * @param transferable The transferable to use.
     95     */
     96    public void pasteOn(OsmDataLayer editLayer, EastNorth mPosition, Transferable transferable) {
     97        importData(new TransferSupport(Main.panel, transferable), editLayer, mPosition);
     98    }
     99
     100    /**
     101     * Paste the given tags on the primitives.
     102     * @param primitives The primitives to paste on.
     103     */
     104    public void pasteTags(Collection<? extends OsmPrimitive> primitives) {
     105        Transferable transferable = ClipboardUtils.getClipboard().getContents(null);
     106        importTags(new TransferSupport(Main.panel, transferable), primitives);
     107    }
     108
     109    /**
     110     * Check if any primitive data or any other supported data is available in the clippboard.
     111     * @return <code>true</code> if any flavor is supported.
     112     */
     113    public boolean isDataAvailable() {
     114        Collection<DataFlavor> available = Arrays.asList(ClipboardUtils.getClipboard().getAvailableDataFlavors());
     115        for (AbstractOsmDataPaster s : SUPPORTED) {
     116            if (s.supports(available)) {
     117                return true;
     118            }
     119        }
     120        return false;
     121    }
     122}
  • src/org/openstreetmap/josm/gui/datatransfer/PrimitiveTransferable.java

    diff --git a/src/org/openstreetmap/josm/gui/datatransfer/PrimitiveTransferable.java b/src/org/openstreetmap/josm/gui/datatransfer/PrimitiveTransferable.java
    index cd91a24..21e302a 100644
    a b package org.openstreetmap.josm.gui.datatransfer;  
    44import java.awt.datatransfer.DataFlavor;
    55import java.awt.datatransfer.Transferable;
    66import java.awt.datatransfer.UnsupportedFlavorException;
    7 import java.io.Serializable;
    87import java.util.ArrayList;
    9 import java.util.Collection;
     8import java.util.Arrays;
     9import java.util.List;
    1010
    11 import org.openstreetmap.josm.data.osm.OsmPrimitive;
     11import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
    1212import org.openstreetmap.josm.data.osm.PrimitiveData;
    13 import org.openstreetmap.josm.gui.DefaultNameFormatter;
    14 import org.openstreetmap.josm.tools.CheckParameterUtil;
     13import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData;
     14import org.openstreetmap.josm.gui.datatransfer.data.TagTransferData;
     15import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    1516
    1617/**
    17  * Transferable objects for {@link PrimitiveData}.
     18 * Transferable objects for {@link PrimitiveTransferData} objects
    1819 * @since 9369
     20 * @since xxx Complete rework
    1921 */
    2022public class PrimitiveTransferable implements Transferable {
    2123
    2224    /**
    23      * A wrapper for a collection of {@link PrimitiveData}.
     25     * The flavors that are available for normal primitives.
    2426     */
    25     public static final class Data implements Serializable {
    26         private static final long serialVersionUID = -1485089993600213704L;
    27         private final Collection<PrimitiveData> primitiveData;
    28 
    29         private Data(Collection<PrimitiveData> primitiveData) {
    30             CheckParameterUtil.ensureThat(primitiveData instanceof Serializable, "primitiveData must be instanceof Serializable");
    31             this.primitiveData = primitiveData;
    32         }
    33 
    34         /**
    35          * Returns the contained {@link PrimitiveData}
    36          * @return the contained {@link PrimitiveData}
    37          */
    38         public Collection<PrimitiveData> getPrimitiveData() {
    39             return primitiveData;
    40         }
    41     }
     27    private static final List<DataFlavor> PRIMITIVE_FLAVORS = Arrays.asList(PrimitiveTransferData.DATA_FLAVOR,
     28            TagTransferData.FLAVOR, DataFlavor.stringFlavor);
     29    private final PrimitiveTransferData primitives;
     30    private OsmDataLayer sourceLayer;
    4231
    4332    /**
    44      * Data flavor for {@link PrimitiveData} which is wrapped in {@link Data}.
     33     * Constructs a new {@code PrimitiveTransferable}.
     34     * @param primitives collection of OSM primitives
    4535     */
    46     public static final DataFlavor PRIMITIVE_DATA = new DataFlavor(Data.class, Data.class.getName());
    47     private final Collection<? extends OsmPrimitive> primitives;
     36    public PrimitiveTransferable(PrimitiveTransferData primitives) {
     37        this(primitives, null);
     38    }
    4839
    4940    /**
    5041     * Constructs a new {@code PrimitiveTransferable}.
    5142     * @param primitives collection of OSM primitives
     43     * @param sourceLayer The layer the primitives are copied from.
    5244     */
    53     public PrimitiveTransferable(Collection<? extends OsmPrimitive> primitives) {
     45    public PrimitiveTransferable(PrimitiveTransferData primitives, OsmDataLayer sourceLayer) {
    5446        this.primitives = primitives;
     47        this.sourceLayer = sourceLayer;
    5548    }
    5649
    5750    @Override
    5851    public DataFlavor[] getTransferDataFlavors() {
    59         return new DataFlavor[]{PRIMITIVE_DATA, DataFlavor.stringFlavor};
     52        ArrayList<DataFlavor> flavors = new ArrayList<>(PRIMITIVE_FLAVORS);
     53        return flavors.toArray(new DataFlavor[flavors.size()]);
    6054    }
    6155
    6256    @Override
    6357    public boolean isDataFlavorSupported(DataFlavor flavor) {
    64         return flavor == PRIMITIVE_DATA;
     58        DataFlavor[] flavors = getTransferDataFlavors();
     59        for (DataFlavor f : flavors) {
     60            if (flavor.equals(f)) {
     61                return true;
     62            }
     63        }
     64        return false;
    6565    }
    6666
    6767    @Override
    6868    public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException {
    6969        if (DataFlavor.stringFlavor.equals(flavor)) {
    7070            return getStringData();
    71         } else if (PRIMITIVE_DATA.equals(flavor)) {
    72             return getPrimitiveData();
     71        } else if (PrimitiveTransferData.DATA_FLAVOR.equals(flavor)) {
     72            return primitives;
     73        } else if (TagTransferData.FLAVOR.equals(flavor)) {
     74            return new TagTransferData(primitives.getDirectlyAdded());
     75        } else {
     76            throw new UnsupportedFlavorException(flavor);
    7377        }
    74         throw new UnsupportedFlavorException(flavor);
    7578    }
    7679
    7780    protected String getStringData() {
    7881        final StringBuilder sb = new StringBuilder();
    79         for (OsmPrimitive primitive : primitives) {
    80             sb.append(primitive.getType())
    81               .append(' ').append(primitive.getUniqueId())
    82               .append(" # ").append(primitive.getDisplayName(DefaultNameFormatter.getInstance()))
    83               .append('\n');
     82        for (PrimitiveData primitive : primitives.getAll()) {
     83            if (sb.length() > 0) {
     84                sb.append("\n");
     85            }
     86            sb.append(OsmPrimitiveType.from(primitive).getAPIName()).append(' ').append(primitive.getId());
    8487        }
    8588        return sb.toString().replace("\u200E", "").replace("\u200F", "");
    8689    }
    87 
    88     protected Data getPrimitiveData() {
    89         final Collection<PrimitiveData> r = new ArrayList<>(primitives.size());
    90         for (OsmPrimitive primitive : primitives) {
    91             r.add(primitive.save());
    92         }
    93         return new Data(r);
    94     }
    9590}
  • src/org/openstreetmap/josm/gui/datatransfer/RelationMemberTransferable.java

    diff --git a/src/org/openstreetmap/josm/gui/datatransfer/RelationMemberTransferable.java b/src/org/openstreetmap/josm/gui/datatransfer/RelationMemberTransferable.java
    index 46dc5d2..f7b78c1 100644
    a b import java.awt.datatransfer.Transferable;  
    66import java.awt.datatransfer.UnsupportedFlavorException;
    77import java.io.Serializable;
    88import java.util.ArrayList;
     9import java.util.Arrays;
    910import java.util.Collection;
     11import java.util.Collections;
     12import java.util.HashSet;
    1013
     14import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1115import org.openstreetmap.josm.data.osm.RelationMember;
    1216import org.openstreetmap.josm.data.osm.RelationMemberData;
    1317import org.openstreetmap.josm.gui.DefaultNameFormatter;
     18import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData;
    1419import org.openstreetmap.josm.tools.CheckParameterUtil;
    1520
    1621/**
    public class RelationMemberTransferable implements Transferable {  
    3641         * @return the contained {@link RelationMemberData}
    3742         */
    3843        public Collection<RelationMemberData> getRelationMemberData() {
    39             return relationMemberDatas;
     44            return Collections.unmodifiableCollection(relationMemberDatas);
     45        }
     46
     47        /**
     48         * Gets the Data for the given list of members.
     49         * @param members The collection. The order is preserved.
     50         * @return The data.
     51         */
     52        public static Data getData(Collection<RelationMember> members) {
     53            final Collection<RelationMemberData> r = new ArrayList<>(members.size());
     54            for (RelationMember member : members) {
     55                r.add(new RelationMemberData(member.getRole(), member.getType(), member.getUniqueId()));
     56            }
     57            return new Data(r);
    4058        }
    4159    }
    4260
    4361    /**
    4462     * Data flavor for {@link RelationMemberData} which is wrapped in {@link Data}.
    4563     */
    46     public static final DataFlavor RELATION_MEMBER_DATA = new DataFlavor(Data.class, Data.class.getName());
     64    public static final DataFlavor RELATION_MEMBER_DATA = new DataFlavor(Data.class, "Relation member");
    4765    private final Collection<RelationMember> members;
    4866
    4967    /**
    public class RelationMemberTransferable implements Transferable {  
    5169     * @param members list of relation members
    5270     */
    5371    public RelationMemberTransferable(Collection<RelationMember> members) {
    54         this.members = members;
     72        this.members = new ArrayList<>(members);
    5573    }
    5674
    5775    @Override
    5876    public DataFlavor[] getTransferDataFlavors() {
    59         return new DataFlavor[]{RELATION_MEMBER_DATA, DataFlavor.stringFlavor};
     77        return new DataFlavor[]{RELATION_MEMBER_DATA, PrimitiveTransferData.DATA_FLAVOR, DataFlavor.stringFlavor};
    6078    }
    6179
    6280    @Override
    6381    public boolean isDataFlavorSupported(DataFlavor flavor) {
    64         return flavor == RELATION_MEMBER_DATA;
     82        return Arrays.asList(getTransferDataFlavors()).contains(flavor);
    6583    }
    6684
    6785    @Override
    public class RelationMemberTransferable implements Transferable {  
    7088            return getStringData();
    7189        } else if (RELATION_MEMBER_DATA.equals(flavor)) {
    7290            return getRelationMemberData();
     91        } else if (PrimitiveTransferData.DATA_FLAVOR.equals(flavor)) {
     92            return getPrimitiveData();
    7393        }
    7494        throw new UnsupportedFlavorException(flavor);
    7595    }
    7696
     97    private PrimitiveTransferData getPrimitiveData() {
     98        Collection<OsmPrimitive> primitives = new HashSet<>();
     99        for (RelationMember member : members) {
     100            primitives.add(member.getMember());
     101        }
     102        return PrimitiveTransferData.getData(primitives);
     103    }
     104
    77105    protected String getStringData() {
    78106        final StringBuilder sb = new StringBuilder();
    79107        for (RelationMember member : members) {
    public class RelationMemberTransferable implements Transferable {  
    87115    }
    88116
    89117    protected Data getRelationMemberData() {
    90         final Collection<RelationMemberData> r = new ArrayList<>(members.size());
    91         for (RelationMember member : members) {
    92             r.add(new RelationMemberData(member.getRole(), member.getType(), member.getUniqueId()));
    93         }
    94         return new Data(r);
     118        return Data.getData(members);
    95119    }
    96120}
  • new file src/org/openstreetmap/josm/gui/datatransfer/data/PrimitiveTransferData.java

    diff --git a/src/org/openstreetmap/josm/gui/datatransfer/data/PrimitiveTransferData.java b/src/org/openstreetmap/josm/gui/datatransfer/data/PrimitiveTransferData.java
    new file mode 100644
    index 0000000..ff68f09
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.datatransfer.data;
     3
     4import java.awt.datatransfer.DataFlavor;
     5import java.io.Serializable;
     6import java.util.ArrayList;
     7import java.util.Collection;
     8import java.util.Collections;
     9import java.util.HashSet;
     10import java.util.LinkedList;
     11import java.util.Queue;
     12
     13import org.openstreetmap.josm.data.ProjectionBounds;
     14import org.openstreetmap.josm.data.coor.EastNorth;
     15import org.openstreetmap.josm.data.osm.NodeData;
     16import org.openstreetmap.josm.data.osm.OsmPrimitive;
     17import org.openstreetmap.josm.data.osm.PrimitiveData;
     18import org.openstreetmap.josm.data.osm.Relation;
     19import org.openstreetmap.josm.data.osm.Way;
     20import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
     21
     22/**
     23 * A list of primitives that are transfered. The list allows you to implicitly add primitives.
     24 * It distingluishes between primitives that were directly added and implicitly added ones.
     25 * @author Michael Zangl
     26 * @since xxx
     27 */
     28public final class PrimitiveTransferData implements Serializable {
     29    private static final long serialVersionUID = 1L;
     30
     31    /**
     32     * The data flavor used to represent this class.
     33     */
     34    public static final DataFlavor DATA_FLAVOR = new DataFlavor(PrimitiveTransferData.class, "OSM Primitives");
     35
     36    private static final class GetReferences implements ReferenceGetter {
     37        @Override
     38        public Collection<? extends OsmPrimitive> getReferedPrimitives(OsmPrimitive primitive) {
     39            if (primitive instanceof Way) {
     40                return ((Way) primitive).getNodes();
     41            } else if (primitive instanceof Relation) {
     42                return ((Relation) primitive).getMemberPrimitives();
     43            } else {
     44                return Collections.emptyList();
     45            }
     46        }
     47    }
     48
     49    // TODO: Java8: Replace by Function.
     50    private interface ReferenceGetter {
     51        Collection<? extends OsmPrimitive> getReferedPrimitives(OsmPrimitive primitive);
     52    }
     53
     54    private final ArrayList<PrimitiveData> direct;
     55    private final ArrayList<PrimitiveData> referenced;
     56
     57    /**
     58     * Create the new transfer data.
     59     * @param primitives The primitives to transfer
     60     * @param referencedGetter A function that allows to get the primitives referenced by the primitives variable.
     61     * It will be queried recursively.
     62     */
     63    private PrimitiveTransferData(Collection<? extends OsmPrimitive> primitives, ReferenceGetter referencedGetter) {
     64        // convert to hash set first to remove dupplicates
     65        HashSet<OsmPrimitive> visited = new HashSet<OsmPrimitive>(primitives);
     66        this.direct = new ArrayList<>(visited.size());
     67
     68        this.referenced = new ArrayList<>();
     69        Queue<OsmPrimitive> toCheck = new LinkedList<>();
     70        for (OsmPrimitive p : visited) {
     71            direct.add(p.save());
     72            toCheck.addAll(referencedGetter.getReferedPrimitives(p));
     73        }
     74        while (!toCheck.isEmpty()) {
     75            OsmPrimitive p = toCheck.poll();
     76            if (visited.add(p)) {
     77                referenced.add(p.save());
     78                toCheck.addAll(referencedGetter.getReferedPrimitives(p));
     79            }
     80        }
     81    }
     82
     83    /**
     84     * Gets all primitives directly added.
     85     * @return The primitives
     86     */
     87    public Collection<PrimitiveData> getDirectlyAdded() {
     88        return Collections.unmodifiableList(direct);
     89    }
     90
     91    /**
     92     * Gets all primitives that were added because they were referenced.
     93     * @return The primitives
     94     */
     95    public Collection<PrimitiveData> getReferenced() {
     96        return Collections.unmodifiableList(referenced);
     97    }
     98
     99    /**
     100     * Gets a List of all primitives added to this set.
     101     * @return That list.
     102     */
     103    public Collection<PrimitiveData> getAll() {
     104        ArrayList<PrimitiveData> list = new ArrayList<>();
     105        list.addAll(direct);
     106        list.addAll(referenced);
     107        return list;
     108    }
     109
     110    /**
     111     * Creates a new {@link PrimitiveTransferData} object that only contains the primitives.
     112     * @param primitives The primitives to contain.
     113     * @return That set.
     114     */
     115    public static PrimitiveTransferData getData(Collection<? extends OsmPrimitive> primitives) {
     116        return new PrimitiveTransferData(primitives, new ReferenceGetter() {
     117            @Override
     118            public Collection<? extends OsmPrimitive> getReferedPrimitives(OsmPrimitive primitive) {
     119                return Collections.emptyList();
     120            }
     121        });
     122    }
     123
     124
     125    /**
     126     * Creates a new {@link PrimitiveTransferData} object that contains the primitives and all references.
     127     * @param primitives The primitives to contain.
     128     * @return That set.
     129     */
     130    public static PrimitiveTransferData getDataWithReferences(Collection<? extends OsmPrimitive> primitives) {
     131        return new PrimitiveTransferData(primitives, new GetReferences());
     132    }
     133
     134    /**
     135     * Compute the center of all nodes.
     136     * @return The center or null if this buffer has no location.
     137     */
     138    public EastNorth getCenter() {
     139        BoundingXYVisitor visitor = new BoundingXYVisitor();
     140        for (PrimitiveData pd : getAll()) {
     141            if (pd instanceof NodeData && !pd.isIncomplete()) {
     142                visitor.visit(((NodeData) pd).getEastNorth());
     143            }
     144        }
     145        ProjectionBounds bounds = visitor.getBounds();
     146        if (bounds == null) {
     147            return null;
     148        } else {
     149            return bounds.getCenter();
     150        }
     151    }
     152
     153    /**
     154     * Tests wheter this set contains any primitives that have invalid data.
     155     * @return <code>true</code> if invalid data is contained in this set.
     156     */
     157    public boolean hasIncompleteData() {
     158        for (PrimitiveData pd : getAll()) {
     159            if (pd.isIncomplete()) {
     160                return true;
     161            }
     162        }
     163        return false;
     164    }
     165}
  • new file src/org/openstreetmap/josm/gui/datatransfer/data/TagTransferData.java

    diff --git a/src/org/openstreetmap/josm/gui/datatransfer/data/TagTransferData.java b/src/org/openstreetmap/josm/gui/datatransfer/data/TagTransferData.java
    new file mode 100644
    index 0000000..531d595
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.datatransfer.data;
     3
     4import java.awt.datatransfer.DataFlavor;
     5import java.io.Serializable;
     6import java.util.Collection;
     7import java.util.Collections;
     8import java.util.Map;
     9
     10import org.openstreetmap.josm.data.osm.TagMap;
     11import org.openstreetmap.josm.data.osm.Tagged;
     12
     13/**
     14 * This is a special transfer type that only transfers tag data.
     15 * <p>
     16 * It currently contains all tags contained in the selection that was copied.
     17 * @author Michael Zangl
     18 * @since xxx
     19 */
     20public class TagTransferData implements Serializable {
     21
     22    private static final long serialVersionUID = 1;
     23
     24    /**
     25     * This is a data flavor added
     26     */
     27    public static final DataFlavor FLAVOR = new DataFlavor(TagTransferData.class, "OSM Tags");
     28
     29    private final TagMap tags = new TagMap();
     30
     31    /**
     32     * Creates a new {@link TagTransferData} object for the given objects.
     33     * @param tagged The tags to transfer.
     34     */
     35    public TagTransferData(Collection<? extends Tagged> tagged) {
     36        for (Tagged t : tagged) {
     37            tags.putAll(t.getKeys());
     38        }
     39    }
     40
     41    /**
     42     * Gets all tags contained in this data.
     43     * @return The tags.
     44     */
     45    public Map<String, String> getTags() {
     46        return Collections.unmodifiableMap(tags);
     47    }
     48}
  • new file src/org/openstreetmap/josm/gui/datatransfer/importers/AbstractOsmDataPaster.java

    diff --git a/src/org/openstreetmap/josm/gui/datatransfer/importers/AbstractOsmDataPaster.java b/src/org/openstreetmap/josm/gui/datatransfer/importers/AbstractOsmDataPaster.java
    new file mode 100644
    index 0000000..d370a39
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.datatransfer.importers;
     3
     4import java.awt.datatransfer.DataFlavor;
     5import java.awt.datatransfer.UnsupportedFlavorException;
     6import java.io.IOException;
     7import java.util.Collection;
     8
     9import javax.swing.TransferHandler;
     10import javax.swing.TransferHandler.TransferSupport;
     11
     12import org.openstreetmap.josm.data.coor.EastNorth;
     13import org.openstreetmap.josm.data.osm.OsmPrimitive;
     14import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     15
     16/**
     17 * This is an abstract class that helps implementing the transfer support required by swing.
     18 * <p>
     19 * It implements a mechanism to import a given data flavor into the current OSM data layer.
     20 * @author Michael Zangl
     21 * @since xxx
     22 */
     23public abstract class AbstractOsmDataPaster {
     24    protected final DataFlavor df;
     25
     26    /**
     27     * Create a new {@link AbstractOsmDataPaster}
     28     * @param df The data flavor that this support supports.
     29     */
     30    protected AbstractOsmDataPaster(DataFlavor df) {
     31        this.df = df;
     32    }
     33
     34    /**
     35     * Checks if this supports importing the given transfer support.
     36     * @param support The support that should be supported.
     37     * @return True if we support that transfer.
     38     */
     39    public boolean supports(TransferSupport support) {
     40        return support.isDataFlavorSupported(df) && isCopy(support);
     41    }
     42
     43    /**
     44     * Checks if this supports any of the available flavors.
     45     * @param available The flavors that should be supported
     46     * @return True if any of them is supported.
     47     */
     48    public boolean supports(Collection<DataFlavor> available) {
     49        return available.contains(df);
     50    }
     51
     52    private static boolean isCopy(TransferSupport support) {
     53        return !support.isDrop() || (TransferHandler.COPY & support.getSourceDropActions()) == TransferHandler.COPY;
     54    }
     55
     56    /**
     57     * Attempts to import the given transfer data.
     58     * @param support The transfer support to import from.
     59     * @param layer The layer to paste at.
     60     * @param pasteAt The position to paste at.
     61     * @return <code>true</code> if the import was successful.
     62     * @throws UnsupportedFlavorException
     63     * @throws IOException
     64     */
     65    public abstract boolean importData(TransferSupport support, OsmDataLayer layer, EastNorth pasteAt)
     66            throws UnsupportedFlavorException, IOException;
     67
     68    /**
     69     * Imports only if this import changes the tags only. Does nothing if more than tags would be changed.
     70     * @param support The support
     71     * @param selection The primitives to apply on.
     72     * @return <code>true</code> if an import was done.
     73     * @throws UnsupportedFlavorException
     74     * @throws IOException
     75     */
     76    public boolean importTagsOn(TransferSupport support, Collection<? extends OsmPrimitive> selection)
     77            throws UnsupportedFlavorException, IOException {
     78        return false;
     79    }
     80}
  • new file src/org/openstreetmap/josm/gui/datatransfer/importers/AbstractTagPaster.java

    diff --git a/src/org/openstreetmap/josm/gui/datatransfer/importers/AbstractTagPaster.java b/src/org/openstreetmap/josm/gui/datatransfer/importers/AbstractTagPaster.java
    new file mode 100644
    index 0000000..dfc4cc7
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.datatransfer.importers;
     3
     4import java.awt.datatransfer.DataFlavor;
     5import java.awt.datatransfer.UnsupportedFlavorException;
     6import java.io.IOException;
     7import java.util.Collection;
     8import java.util.Map;
     9
     10import javax.swing.TransferHandler.TransferSupport;
     11
     12import org.openstreetmap.josm.Main;
     13import org.openstreetmap.josm.command.ChangePropertyCommand;
     14import org.openstreetmap.josm.data.coor.EastNorth;
     15import org.openstreetmap.josm.data.osm.OsmPrimitive;
     16import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     17
     18/**
     19 * This transfer support allows us to transfer tags to the selected primitives
     20 * @author Michael Zangl
     21 * @since xxx
     22 */
     23public abstract class AbstractTagPaster extends AbstractOsmDataPaster {
     24
     25    AbstractTagPaster(DataFlavor df) {
     26        super(df);
     27    }
     28
     29    @Override
     30    public boolean importData(TransferSupport support, OsmDataLayer layer, EastNorth pasteAt)
     31            throws UnsupportedFlavorException, IOException {
     32        Collection<OsmPrimitive> selection = layer.data.getSelected();
     33        if (selection.isEmpty()) {
     34            return false;
     35        }
     36
     37        return importTagsOn(support, selection);
     38    }
     39
     40    @Override
     41    public boolean importTagsOn(TransferSupport support, Collection<? extends OsmPrimitive> selection)
     42            throws UnsupportedFlavorException, IOException {
     43        ChangePropertyCommand command = new ChangePropertyCommand(selection, getTags(support));
     44        Main.main.undoRedo.add(command);
     45        return true;
     46    }
     47
     48    /**
     49     * Gets the tags that should be pasted.
     50     * @param support The TransferSupport to get the tags from.
     51     * @return The tags
     52     * @throws UnsupportedFlavorException
     53     * @throws IOException
     54     */
     55    protected abstract Map<String, String> getTags(TransferSupport support) throws UnsupportedFlavorException, IOException;
     56}
  • new file src/org/openstreetmap/josm/gui/datatransfer/importers/FilePaster.java

    diff --git a/src/org/openstreetmap/josm/gui/datatransfer/importers/FilePaster.java b/src/org/openstreetmap/josm/gui/datatransfer/importers/FilePaster.java
    new file mode 100644
    index 0000000..80ee372
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.datatransfer.importers;
     3
     4import java.awt.datatransfer.DataFlavor;
     5import java.awt.datatransfer.UnsupportedFlavorException;
     6import java.io.File;
     7import java.io.IOException;
     8import java.util.List;
     9
     10import javax.swing.TransferHandler.TransferSupport;
     11
     12import org.openstreetmap.josm.Main;
     13import org.openstreetmap.josm.actions.OpenFileAction;
     14import org.openstreetmap.josm.data.coor.EastNorth;
     15import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     16
     17/**
     18 * This transfer support allows us to import a file that is dropped / copied on to the map.
     19 * @author Michael Zangl
     20 * @since xxx
     21 */
     22public final class FilePaster extends AbstractOsmDataPaster {
     23    /**
     24     * Create a new {@link FilePaster}
     25     */
     26    public FilePaster() {
     27        super(DataFlavor.javaFileListFlavor);
     28    }
     29
     30    @Override
     31    public boolean importData(TransferSupport support, OsmDataLayer layer, EastNorth pasteAt)
     32            throws UnsupportedFlavorException, IOException {
     33        @SuppressWarnings("unchecked")
     34        List<File> files = (List<File>) support.getTransferable().getTransferData(df);
     35        OpenFileAction.OpenFileTask task = new OpenFileAction.OpenFileTask(files, null);
     36        task.setRecordHistory(true);
     37        Main.worker.submit(task);
     38        return true;
     39    }
     40
     41}
  • new file src/org/openstreetmap/josm/gui/datatransfer/importers/PrimitiveDataPaster.java

    diff --git a/src/org/openstreetmap/josm/gui/datatransfer/importers/PrimitiveDataPaster.java b/src/org/openstreetmap/josm/gui/datatransfer/importers/PrimitiveDataPaster.java
    new file mode 100644
    index 0000000..5209c68
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.datatransfer.importers;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.awt.datatransfer.UnsupportedFlavorException;
     7import java.io.IOException;
     8import java.util.ArrayList;
     9import java.util.EnumMap;
     10import java.util.HashMap;
     11import java.util.List;
     12import java.util.Map;
     13
     14import javax.swing.TransferHandler.TransferSupport;
     15
     16import org.openstreetmap.josm.Main;
     17import org.openstreetmap.josm.command.AddPrimitivesCommand;
     18import org.openstreetmap.josm.data.coor.EastNorth;
     19import org.openstreetmap.josm.data.osm.NodeData;
     20import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
     21import org.openstreetmap.josm.data.osm.PrimitiveData;
     22import org.openstreetmap.josm.data.osm.RelationData;
     23import org.openstreetmap.josm.data.osm.RelationMemberData;
     24import org.openstreetmap.josm.data.osm.WayData;
     25import org.openstreetmap.josm.gui.ExtendedDialog;
     26import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData;
     27import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     28
     29/**
     30 * This transfer support allows us to transfer primitives. This is the default paste action when primitives were copied.
     31 * @author Michael Zangl
     32 * @since xxx
     33 */
     34public final class PrimitiveDataPaster extends AbstractOsmDataPaster {
     35    /**
     36     * Create a new {@link PrimitiveDataPaster}
     37     */
     38    public PrimitiveDataPaster() {
     39        super(PrimitiveTransferData.DATA_FLAVOR);
     40    }
     41
     42    @Override
     43    public boolean importData(TransferSupport support, final OsmDataLayer layer, EastNorth pasteAt)
     44            throws UnsupportedFlavorException, IOException {
     45        PrimitiveTransferData pasteBuffer = (PrimitiveTransferData) support.getTransferable().getTransferData(df);
     46        // Allow to cancel paste if there are incomplete primitives
     47        if (pasteBuffer.hasIncompleteData() && !confirmDeleteIncomplete()) {
     48            return false;
     49        }
     50
     51        EastNorth center = pasteBuffer.getCenter();
     52        EastNorth offset = center == null ? null : pasteAt.subtract(center);
     53
     54        AddPrimitivesCommand command = createNewPrimitives(pasteBuffer, offset, layer);
     55
     56        /* Now execute the commands to add the duplicated contents of the paste buffer to the map */
     57        Main.main.undoRedo.add(command);
     58        return true;
     59    }
     60
     61    private static AddPrimitivesCommand createNewPrimitives(PrimitiveTransferData pasteBuffer, EastNorth offset, OsmDataLayer layer) {
     62        // Make a copy of pasteBuffer and map from old id to copied data id
     63        List<PrimitiveData> bufferCopy = new ArrayList<>();
     64        List<PrimitiveData> toSelect = new ArrayList<>();
     65        EnumMap<OsmPrimitiveType, Map<Long, Long>> newIds = generateNewPrimitives(pasteBuffer, bufferCopy, toSelect);
     66
     67        // Update references in copied buffer
     68        for (PrimitiveData data : bufferCopy) {
     69            if (data instanceof NodeData) {
     70                NodeData nodeData = (NodeData) data;
     71                nodeData.setEastNorth(nodeData.getEastNorth().add(offset));
     72            } else if (data instanceof WayData) {
     73                updateNodes(newIds.get(OsmPrimitiveType.NODE), data);
     74            } else if (data instanceof RelationData) {
     75                updateMembers(newIds, data);
     76            }
     77        }
     78        return new AddPrimitivesCommand(bufferCopy, toSelect, layer);
     79    }
     80
     81    private static EnumMap<OsmPrimitiveType, Map<Long, Long>> generateNewPrimitives(PrimitiveTransferData pasteBuffer,
     82            List<PrimitiveData> bufferCopy, List<PrimitiveData> toSelect) {
     83        EnumMap<OsmPrimitiveType, Map<Long, Long>> newIds = new EnumMap<>(OsmPrimitiveType.class);
     84        newIds.put(OsmPrimitiveType.NODE, new HashMap<Long, Long>());
     85        newIds.put(OsmPrimitiveType.WAY, new HashMap<Long, Long>());
     86        newIds.put(OsmPrimitiveType.RELATION, new HashMap<Long, Long>());
     87
     88        for (PrimitiveData data : pasteBuffer.getAll()) {
     89            if (data.isIncomplete()) {
     90                continue;
     91            }
     92            PrimitiveData copy = data.makeCopy();
     93            // don't know why this is reset, but we need it to not crash on copying incomplete nodes.
     94            boolean wasIncomplete = copy.isIncomplete();
     95            copy.clearOsmMetadata();
     96            copy.setIncomplete(wasIncomplete);
     97            newIds.get(data.getType()).put(data.getUniqueId(), copy.getUniqueId());
     98
     99            bufferCopy.add(copy);
     100            if (pasteBuffer.getDirectlyAdded().contains(data)) {
     101                toSelect.add(copy);
     102            }
     103        }
     104        return newIds;
     105    }
     106
     107    private static void updateMembers(EnumMap<OsmPrimitiveType, Map<Long, Long>> newIds, PrimitiveData data) {
     108        List<RelationMemberData> newMembers = new ArrayList<>();
     109        for (RelationMemberData member : ((RelationData) data).getMembers()) {
     110            OsmPrimitiveType memberType = member.getMemberType();
     111            Long newId = newIds.get(memberType).get(member.getMemberId());
     112            if (newId != null) {
     113                newMembers.add(new RelationMemberData(member.getRole(), memberType, newId));
     114            }
     115        }
     116        ((RelationData) data).setMembers(newMembers);
     117    }
     118
     119    private static void updateNodes(Map<Long, Long> newNodeIds, PrimitiveData data) {
     120        List<Long> newNodes = new ArrayList<>();
     121        for (Long oldNodeId : ((WayData) data).getNodes()) {
     122            Long newNodeId = newNodeIds.get(oldNodeId);
     123            if (newNodeId != null) {
     124                newNodes.add(newNodeId);
     125            }
     126        }
     127        ((WayData) data).setNodes(newNodes);
     128    }
     129
     130    private static boolean confirmDeleteIncomplete() {
     131        ExtendedDialog ed = new ExtendedDialog(Main.parent, tr("Delete incomplete members?"),
     132                new String[] { tr("Paste without incomplete members"), tr("Cancel") });
     133        ed.setButtonIcons(new String[] { "dialogs/relation/deletemembers", "cancel" });
     134        ed.setContent(tr(
     135                "The copied data contains incomplete objects.  " + "When pasting the incomplete objects are removed.  "
     136                        + "Do you want to paste the data without the incomplete objects?"));
     137        ed.showDialog();
     138        return ed.getValue() == 1;
     139    }
     140}
  • new file src/org/openstreetmap/josm/gui/datatransfer/importers/TagTransferPaster.java

    diff --git a/src/org/openstreetmap/josm/gui/datatransfer/importers/TagTransferPaster.java b/src/org/openstreetmap/josm/gui/datatransfer/importers/TagTransferPaster.java
    new file mode 100644
    index 0000000..23f9fd7
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.datatransfer.importers;
     3
     4import java.awt.datatransfer.UnsupportedFlavorException;
     5import java.io.IOException;
     6import java.util.Map;
     7
     8import javax.swing.TransferHandler.TransferSupport;
     9
     10import org.openstreetmap.josm.gui.datatransfer.data.TagTransferData;
     11
     12/**
     13 * This transfer support allows us to transfer tags from the copied primitives on to the selected ones.
     14 * @author Michael Zangl
     15 * @since xxx
     16 */
     17public final class TagTransferPaster extends AbstractTagPaster {
     18    /**
     19     * Create a new {@link TagTransferPaster}
     20     */
     21    public TagTransferPaster() {
     22        super(TagTransferData.FLAVOR);
     23    }
     24
     25    @Override
     26    protected Map<String, String> getTags(TransferSupport support) throws UnsupportedFlavorException, IOException {
     27        TagTransferData data = (TagTransferData) support.getTransferable().getTransferData(df);
     28        return data.getTags();
     29    }
     30}
  • new file src/org/openstreetmap/josm/gui/datatransfer/importers/TextTagPaster.java

    diff --git a/src/org/openstreetmap/josm/gui/datatransfer/importers/TextTagPaster.java b/src/org/openstreetmap/josm/gui/datatransfer/importers/TextTagPaster.java
    new file mode 100644
    index 0000000..cadc706
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.datatransfer.importers;
     3
     4import java.awt.datatransfer.DataFlavor;
     5import java.awt.datatransfer.UnsupportedFlavorException;
     6import java.io.IOException;
     7import java.util.Map;
     8
     9import javax.swing.TransferHandler.TransferSupport;
     10
     11import org.openstreetmap.josm.Main;
     12import org.openstreetmap.josm.tools.TextTagParser;
     13
     14/**
     15 * This transfer support allows us to import tags from the text that was copied to the clippboard.
     16 * @author Michael Zangl
     17 * @since xxx
     18 */
     19public final class TextTagPaster extends AbstractTagPaster {
     20
     21    /**
     22     * Create a new {@link TextTagPaster}
     23     */
     24    public TextTagPaster() {
     25        super(DataFlavor.stringFlavor);
     26    }
     27
     28    @Override
     29    public boolean supports(TransferSupport support) {
     30        try {
     31            return super.supports(support) && getTags(support) != null;
     32        } catch (UnsupportedFlavorException | IOException e) {
     33            Main.warn(e);
     34            return false;
     35        }
     36    }
     37
     38    @Override
     39    protected Map<String, String> getTags(TransferSupport support) throws UnsupportedFlavorException, IOException {
     40        return TextTagParser.readTagsFromText((String) support.getTransferable().getTransferData(df));
     41    }
     42}
  • src/org/openstreetmap/josm/gui/dialogs/OsmIdSelectionDialog.java

    diff --git a/src/org/openstreetmap/josm/gui/dialogs/OsmIdSelectionDialog.java b/src/org/openstreetmap/josm/gui/dialogs/OsmIdSelectionDialog.java
    index ea87e40..da11869 100644
    a b import org.openstreetmap.josm.data.osm.OsmPrimitiveType;  
    3333import org.openstreetmap.josm.data.osm.PrimitiveId;
    3434import org.openstreetmap.josm.data.osm.SimplePrimitiveId;
    3535import org.openstreetmap.josm.gui.ExtendedDialog;
     36import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
    3637import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
    3738import org.openstreetmap.josm.gui.widgets.HtmlPanel;
    3839import org.openstreetmap.josm.gui.widgets.JosmTextField;
    public class OsmIdSelectionDialog extends ExtendedDialog implements WindowListen  
    201202    }
    202203
    203204    protected void tryToPasteFromClipboard(OsmIdTextField tfId, OsmPrimitiveTypesComboBox cbType) {
    204         String buf = Utils.getClipboardContent();
     205        String buf = ClipboardUtils.getClipboardStringContent();
    205206        if (buf == null || buf.isEmpty()) return;
    206207        if (buf.length() > Main.pref.getInteger("downloadprimitive.max-autopaste-length", 2000)) return;
    207208        final List<SimplePrimitiveId> ids = SimplePrimitiveId.fuzzyParse(buf);
  • src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java

    diff --git a/src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java b/src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java
    index 3f56870..7935def 100644
    a b import org.openstreetmap.josm.gui.OsmPrimitivRenderer;  
    6969import org.openstreetmap.josm.gui.PopupMenuHandler;
    7070import org.openstreetmap.josm.gui.SideButton;
    7171import org.openstreetmap.josm.gui.datatransfer.PrimitiveTransferable;
     72import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData;
    7273import org.openstreetmap.josm.gui.history.HistoryBrowserDialogManager;
    7374import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
    7475import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
    public class SelectionListDialog extends ToggleDialog {  
    880881
    881882        @Override
    882883        protected Transferable createTransferable(JComponent c) {
    883             return new PrimitiveTransferable(getSelectedPrimitives());
     884            return new PrimitiveTransferable(PrimitiveTransferData.getData(getSelectedPrimitives()));
    884885        }
    885886    }
    886887}
  • src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java

    diff --git a/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java b/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java
    index 4b96ea6..4408f0a 100644
    a b import org.openstreetmap.josm.gui.DefaultNameFormatter;  
    8686import org.openstreetmap.josm.gui.ExtendedDialog;
    8787import org.openstreetmap.josm.gui.PopupMenuHandler;
    8888import org.openstreetmap.josm.gui.SideButton;
     89import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
    8990import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
    9091import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
    9192import org.openstreetmap.josm.gui.help.HelpUtil;
    implements SelectionChangedListener, ActiveLayerChangeListener, DataSetListenerA  
    12641265                return;
    12651266            String key = editHelper.getDataKey(tagTable.getSelectedRow());
    12661267            Collection<OsmPrimitive> sel = Main.main.getInProgressSelection();
    1267             String clipboard = Utils.getClipboardContent();
     1268            String clipboard = ClipboardUtils.getClipboardStringContent();
    12681269            if (sel.isEmpty() || clipboard == null)
    12691270                return;
    12701271            Main.main.undoRedo.add(new ChangePropertyCommand(sel, key, Utils.strip(clipboard)));
    implements SelectionChangedListener, ActiveLayerChangeListener, DataSetListenerA  
    12941295                }
    12951296            }
    12961297            if (!values.isEmpty()) {
    1297                 Utils.copyToClipboard(Utils.join("\n", values));
     1298                ClipboardUtils.copyString(Utils.join("\n", values));
    12981299            }
    12991300        }
    13001301    }
  • src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java

    diff --git a/src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java b/src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java
    index b00d0ce..a4900a7 100644
    a b import org.openstreetmap.josm.data.preferences.EnumProperty;  
    7676import org.openstreetmap.josm.data.preferences.IntegerProperty;
    7777import org.openstreetmap.josm.data.preferences.StringProperty;
    7878import org.openstreetmap.josm.gui.ExtendedDialog;
     79import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
    7980import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
    8081import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingComboBox;
    8182import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionListItem;
    public class TagEditHelper {  
    583584
    584585        private void selectACComboBoxSavingUnixBuffer(AutoCompletingComboBox cb) {
    585586            // select combobox with saving unix system selection (middle mouse paste)
    586             Clipboard sysSel = GuiHelper.getSystemSelection();
     587            Clipboard sysSel = ClipboardUtils.getSystemSelection();
    587588            if (sysSel != null) {
    588                 Transferable old = Utils.getTransferableContent(sysSel);
     589                Transferable old = ClipboardUtils.getClipboardContent(sysSel);
    589590                cb.requestFocusInWindow();
    590591                cb.getEditor().selectAll();
    591592                if (old != null) {
  • src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java

    diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java b/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java
    index 47b563a..fd0175d 100644
    a b import java.awt.GraphicsEnvironment;  
    1111import java.awt.GridBagConstraints;
    1212import java.awt.GridBagLayout;
    1313import java.awt.Window;
     14import java.awt.datatransfer.Clipboard;
     15import java.awt.datatransfer.FlavorListener;
    1416import java.awt.event.ActionEvent;
    1517import java.awt.event.FocusAdapter;
    1618import java.awt.event.FocusEvent;
    import org.openstreetmap.josm.data.osm.Tag;  
    6163import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
    6264import org.openstreetmap.josm.gui.DefaultNameFormatter;
    6365import org.openstreetmap.josm.gui.MainMenu;
     66import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
    6467import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedAfterSelection;
    6568import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedAtEndAction;
    6669import org.openstreetmap.josm.gui.dialogs.relation.actions.AddSelectedAtStartAction;
    public class GenericRelationEditor extends RelationEditor {  
    152155     * Action for performing the {@link CancelAction}
    153156     */
    154157    private final CancelAction cancelAction;
     158    /**
     159     * A list of listeners that need to be notified on clipboard content changes.
     160     */
     161    private final ArrayList<FlavorListener> clipboardListeners = new ArrayList<>();
    155162
    156163    /**
    157164     * Creates a new relation editor for the given relation. The relation will be saved if the user
    public class GenericRelationEditor extends RelationEditor {  
    273280                getRootPane(), memberTable, selectionTable);
    274281        // CHECKSTYLE.ON: LineLength
    275282
    276         registerCopyPasteAction(new PasteMembersAction(memberTableModel, getLayer(), this) {
     283        registerCopyPasteAction(new PasteMembersAction(memberTable, getLayer(), this) {
    277284            @Override
    278285            public void actionPerformed(ActionEvent e) {
    279286                super.actionPerformed(e);
    public class GenericRelationEditor extends RelationEditor {  
    742749            tagEditorPanel.initAutoCompletion(getLayer());
    743750        }
    744751        super.setVisible(visible);
     752        Clipboard clipboard = ClipboardUtils.getClipboard();
    745753        if (visible) {
    746754            leftButtonToolbar.sortBelowButton.setVisible(ExpertToggleAction.isExpert());
    747755            RelationDialogManager.getRelationDialogManager().positionOnScreen(this);
    public class GenericRelationEditor extends RelationEditor {  
    749757                windowMenuItem = addToWindowMenu(this, getLayer().getName());
    750758            }
    751759            tagEditorPanel.requestFocusInWindow();
     760
     761            for (FlavorListener listener : clipboardListeners) {
     762                clipboard.addFlavorListener(listener);
     763            }
    752764        } else {
    753765            // make sure all registered listeners are unregistered
    754766            //
    public class GenericRelationEditor extends RelationEditor {  
    760772                Main.main.menu.windowMenu.remove(windowMenuItem);
    761773                windowMenuItem = null;
    762774            }
     775            for (FlavorListener listener : clipboardListeners) {
     776                clipboard.removeFlavorListener(listener);
     777            }
    763778            dispose();
    764779        }
    765780    }
    public class GenericRelationEditor extends RelationEditor {  
    823838        }
    824839    }
    825840
    826     private static void registerCopyPasteAction(AbstractAction action, Object actionName, KeyStroke shortcut,
     841    private void registerCopyPasteAction(AbstractAction action, Object actionName, KeyStroke shortcut,
    827842            JRootPane rootPane, JTable... tables) {
    828843        int mods = shortcut.getModifiers();
    829844        int code = shortcut.getKeyCode();
    public class GenericRelationEditor extends RelationEditor {  
    840855            table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(shortcut, actionName);
    841856            table.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(shortcut, actionName);
    842857        }
     858        if (action instanceof FlavorListener) {
     859            clipboardListeners.add((FlavorListener) action);
     860        }
    843861    }
    844862
    845863    /**
  • src/org/openstreetmap/josm/gui/dialogs/relation/MemberTable.java

    diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTable.java b/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTable.java
    index 324d3ff..f46eb66 100644
    a b public class MemberTable extends OsmPrimitivesTable implements IMemberModelListe  
    8585        JPopupMenu menu = super.buildPopupMenu();
    8686        zoomToGap = new ZoomToGapAction();
    8787        registerListeners();
     88        menu.addSeparator();
    8889        getSelectionModel().addListSelectionListener(zoomToGap);
    8990        menu.add(zoomToGap);
    9091        menu.addSeparator();
  • src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java

    diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java b/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java
    index d72479f..3a84cd0 100644
    a b implements TableModelListener, SelectionChangedListener, DataSetListener, OsmPri  
    479479            members.add(idx++, member);
    480480        }
    481481        invalidateConnectionType();
    482         final List<Integer> selection = getSelectedIndices();
    483482        fireTableRowsInserted(index, idx - 1);
    484         setSelectedMembersIdx(selection);
    485483    }
    486484
    487485    public void addMembersAtBeginning(List<? extends OsmPrimitive> primitives) {
  • src/org/openstreetmap/josm/gui/dialogs/relation/MemberTransferHandler.java

    diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTransferHandler.java b/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTransferHandler.java
    index 99fef5f..7f70cd0 100644
    a b import javax.swing.TransferHandler;  
    1616import org.openstreetmap.josm.Main;
    1717import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1818import org.openstreetmap.josm.data.osm.PrimitiveData;
     19import org.openstreetmap.josm.data.osm.PrimitiveId;
    1920import org.openstreetmap.josm.data.osm.RelationMember;
    2021import org.openstreetmap.josm.data.osm.RelationMemberData;
    21 import org.openstreetmap.josm.gui.datatransfer.PrimitiveTransferable;
    2222import org.openstreetmap.josm.gui.datatransfer.RelationMemberTransferable;
    23 import org.openstreetmap.josm.tools.Utils.Function;
     23import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData;
    2424
    25 class MemberTransferHandler extends TransferHandler {
     25/**
     26 * A transfer handler that helps with importing / exporting members for relations.
     27 * @author Michael Zangl
     28 * @since xxx
     29 */
     30public class MemberTransferHandler extends TransferHandler {
    2631
    2732    @Override
    2833    public int getSourceActions(JComponent c) {
    class MemberTransferHandler extends TransferHandler {  
    3742
    3843    @Override
    3944    public boolean canImport(TransferSupport support) {
    40         support.setShowDropLocation(true);
     45        if (support.isDrop()) {
     46            support.setShowDropLocation(true);
     47        }
    4148        return support.isDataFlavorSupported(RelationMemberTransferable.RELATION_MEMBER_DATA)
    42                 || support.isDataFlavorSupported(PrimitiveTransferable.PRIMITIVE_DATA);
     49                || support.isDataFlavorSupported(PrimitiveTransferData.DATA_FLAVOR);
    4350    }
    4451
    4552    @Override
    4653    public boolean importData(TransferSupport support) {
    47         final MemberTable destination = (MemberTable) support.getComponent();
    48         final int insertRow = ((JTable.DropLocation) support.getDropLocation()).getRow();
     54        MemberTable destination = (MemberTable) support.getComponent();
     55        int insertRow = computeInsertionRow(support, destination);
     56
     57        return importDataAt(support, destination, insertRow);
     58
     59    }
     60
     61    private int computeInsertionRow(TransferSupport support, MemberTable destination) {
     62        final int insertRow;
     63        if (support.isDrop()) {
     64            insertRow = ((JTable.DropLocation) support.getDropLocation()).getRow();
     65        } else {
     66            int selection = destination.getSelectedRow();
     67            if (selection < 0) {
     68                // no selection, add at the end.
     69                insertRow = destination.getRowCount();
     70            } else {
     71                insertRow = selection;
     72            }
     73        }
     74        return insertRow;
     75    }
    4976
     77    private boolean importDataAt(TransferSupport support, MemberTable destination, int insertRow) {
    5078        try {
    5179            if (support.isDataFlavorSupported(RelationMemberTransferable.RELATION_MEMBER_DATA)) {
    5280                importRelationMemberData(support, destination, insertRow);
    53             } else if (support.isDataFlavorSupported(PrimitiveTransferable.PRIMITIVE_DATA)) {
     81                return true;
     82            } else if (support.isDataFlavorSupported(PrimitiveTransferData.DATA_FLAVOR)) {
    5483                importPrimitiveData(support, destination, insertRow);
     84                return true;
     85            } else {
     86                return false;
    5587            }
    5688        } catch (IOException | UnsupportedFlavorException e) {
    5789            Main.warn(e);
    5890            return false;
    5991        }
    60 
    61         return true;
    6292    }
    6393
    6494    protected void importRelationMemberData(TransferSupport support, final MemberTable destination, int insertRow)
    6595            throws UnsupportedFlavorException, IOException {
    6696        final RelationMemberTransferable.Data memberData = (RelationMemberTransferable.Data)
    6797                support.getTransferable().getTransferData(RelationMemberTransferable.RELATION_MEMBER_DATA);
    68         importData(destination, insertRow, memberData.getRelationMemberData(), new Function<RelationMemberData, RelationMember>() {
     98        importData(destination, insertRow, memberData.getRelationMemberData(), new AbstractRelationMemberConverter<RelationMemberData>() {
    6999            @Override
    70             public RelationMember apply(RelationMemberData member) {
    71                 final OsmPrimitive p = destination.getLayer().data.getPrimitiveById(member.getUniqueId(), member.getType());
    72                 if (p == null) {
    73                     Main.warn(tr("Cannot add {0} since it is not part of dataset", member));
    74                     return null;
    75                 } else {
    76                     return new RelationMember(member.getRole(), p);
    77                 }
     100            protected RelationMember getMember(MemberTable destination, RelationMemberData data, OsmPrimitive p) {
     101                return new RelationMember(data.getRole(), p);
    78102            }
    79103        });
    80104    }
    81105
    82106    protected void importPrimitiveData(TransferSupport support, final MemberTable destination, int insertRow)
    83107            throws UnsupportedFlavorException, IOException {
    84         final PrimitiveTransferable.Data data = (PrimitiveTransferable.Data)
    85                 support.getTransferable().getTransferData(PrimitiveTransferable.PRIMITIVE_DATA);
    86         importData(destination, insertRow, data.getPrimitiveData(), new Function<PrimitiveData, RelationMember>() {
     108        final PrimitiveTransferData data = (PrimitiveTransferData)
     109                support.getTransferable().getTransferData(PrimitiveTransferData.DATA_FLAVOR);
     110        importData(destination, insertRow, data.getDirectlyAdded(), new AbstractRelationMemberConverter<PrimitiveData>() {
    87111            @Override
    88             public RelationMember apply(PrimitiveData data) {
    89                 final OsmPrimitive p = destination.getLayer().data.getPrimitiveById(data);
    90                 if (p == null) {
    91                     Main.warn(tr("Cannot add {0} since it is not part of dataset", data));
    92                     return null;
    93                 } else {
    94                     return destination.getMemberTableModel().getRelationMemberForPrimitive(p);
    95                 }
     112            protected RelationMember getMember(MemberTable destination, PrimitiveData data, OsmPrimitive p) {
     113                return destination.getMemberTableModel().getRelationMemberForPrimitive(p);
    96114            }
    97115        });
    98116    }
    99117
    100     protected <T> void importData(MemberTable destination, int insertRow,
    101                                   Collection<T> memberData, Function<T, RelationMember> toMemberFunction) {
     118    protected <T extends PrimitiveId> void importData(MemberTable destination, int insertRow,
     119                                  Collection<T> memberData, AbstractRelationMemberConverter<T> toMemberFunction) {
    102120        final Collection<RelationMember> membersToAdd = new ArrayList<>(memberData.size());
    103         for (T i : memberData) {
    104             final RelationMember member = toMemberFunction.apply(i);
     121        for (T data : memberData) {
     122            final RelationMember member = toMemberFunction.importPrimitive(destination, data);
    105123            if (member != null) {
    106124                membersToAdd.add(member);
    107125            }
    class MemberTransferHandler extends TransferHandler {  
    119137        model.remove(source.getSelectedRows());
    120138        model.selectionChanged(null);
    121139    }
     140
     141    private abstract static class AbstractRelationMemberConverter<T extends PrimitiveId> {
     142        protected RelationMember importPrimitive(MemberTable destination, T data) {
     143            final OsmPrimitive p = destination.getLayer().data.getPrimitiveById(data);
     144            if (p == null) {
     145                Main.warn(tr("Cannot add {0} since it is not part of dataset", data));
     146                return null;
     147            } else {
     148                return getMember(destination, data, p);
     149            }
     150        }
     151
     152        protected abstract RelationMember getMember(MemberTable destination, T data, OsmPrimitive p);
     153    }
    122154}
  • src/org/openstreetmap/josm/gui/dialogs/relation/actions/CopyMembersAction.java

    diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/actions/CopyMembersAction.java b/src/org/openstreetmap/josm/gui/dialogs/relation/actions/CopyMembersAction.java
    index 952e554..8b8ba36 100644
    a b package org.openstreetmap.josm.gui.dialogs.relation.actions;  
    44import java.awt.event.ActionEvent;
    55import java.util.Collection;
    66
    7 import org.openstreetmap.josm.actions.CopyAction;
    8 import org.openstreetmap.josm.data.osm.OsmPrimitive;
     7import org.openstreetmap.josm.data.osm.RelationMember;
     8import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
     9import org.openstreetmap.josm.gui.datatransfer.RelationMemberTransferable;
    910import org.openstreetmap.josm.gui.dialogs.relation.IRelationEditor;
    1011import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
    1112import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    public class CopyMembersAction extends AddFromSelectionAction {  
    2829
    2930    @Override
    3031    public void actionPerformed(ActionEvent e) {
    31         final Collection<OsmPrimitive> primitives = memberTableModel.getSelectedChildPrimitives();
    32         if (!primitives.isEmpty()) {
    33             CopyAction.copy(layer, primitives);
     32        final Collection<RelationMember> members = memberTableModel.getSelectedMembers();
     33
     34        if (!members.isEmpty()) {
     35            ClipboardUtils.copy(new RelationMemberTransferable(members));
    3436        }
    3537    }
    3638
  • src/org/openstreetmap/josm/gui/dialogs/relation/actions/PasteMembersAction.java

    diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/actions/PasteMembersAction.java b/src/org/openstreetmap/josm/gui/dialogs/relation/actions/PasteMembersAction.java
    index eaa08d2..2cf4c16 100644
    a b  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.gui.dialogs.relation.actions;
    33
    4 import static org.openstreetmap.josm.tools.I18n.tr;
    5 
     4import java.awt.datatransfer.FlavorEvent;
     5import java.awt.datatransfer.FlavorListener;
    66import java.awt.event.ActionEvent;
    7 import java.util.ArrayList;
    8 import java.util.List;
    97
    10 import javax.swing.JOptionPane;
     8import javax.swing.TransferHandler.TransferSupport;
    119
    12 import org.openstreetmap.josm.Main;
    13 import org.openstreetmap.josm.data.osm.DataSet;
    14 import org.openstreetmap.josm.data.osm.OsmPrimitive;
    15 import org.openstreetmap.josm.data.osm.PrimitiveData;
    16 import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditor.AddAbortException;
     10import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
    1711import org.openstreetmap.josm.gui.dialogs.relation.IRelationEditor;
    18 import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
     12import org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
     13import org.openstreetmap.josm.gui.dialogs.relation.MemberTransferHandler;
    1914import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    2015
    2116/**
    2217 * Paste members.
    2318 * @since 9496
    2419 */
    25 public class PasteMembersAction extends AddFromSelectionAction {
     20public class PasteMembersAction extends AddFromSelectionAction implements FlavorListener {
    2621
    2722    /**
    2823     * Constructs a new {@code PasteMembersAction}.
    29      * @param memberTableModel member table model
     24     * @param memberTable member table
    3025     * @param layer OSM data layer
    3126     * @param editor relation editor
    3227     */
    33     public PasteMembersAction(MemberTableModel memberTableModel, OsmDataLayer layer, IRelationEditor editor) {
    34         super(null, memberTableModel, null, null, null, layer, editor);
     28    public PasteMembersAction(MemberTable memberTable, OsmDataLayer layer, IRelationEditor editor) {
     29        super(memberTable, null, null, null, null, layer, editor);
     30        updateEnabledState();
    3531    }
    3632
    3733    @Override
    3834    public void actionPerformed(ActionEvent e) {
    39         try {
    40             List<PrimitiveData> primitives = Main.pasteBuffer.getDirectlyAdded();
    41             DataSet ds = layer.data;
    42             List<OsmPrimitive> toAdd = new ArrayList<>();
    43             boolean hasNewInOtherLayer = false;
    44 
    45             for (PrimitiveData primitive: primitives) {
    46                 OsmPrimitive primitiveInDs = ds.getPrimitiveById(primitive);
    47                 if (primitiveInDs != null) {
    48                     toAdd.add(primitiveInDs);
    49                 } else if (!primitive.isNew()) {
    50                     OsmPrimitive p = primitive.getType().newInstance(primitive.getUniqueId(), true);
    51                     ds.addPrimitive(p);
    52                     toAdd.add(p);
    53                 } else {
    54                     hasNewInOtherLayer = true;
    55                     break;
    56                 }
    57             }
    58 
    59             if (hasNewInOtherLayer) {
    60                 JOptionPane.showMessageDialog(Main.parent,
    61                         tr("Members from paste buffer cannot be added because they are not included in current layer"));
    62                 return;
    63             }
    64 
    65             toAdd = filterConfirmedPrimitives(toAdd);
    66             int index = memberTableModel.getSelectionModel().getMaxSelectionIndex();
    67             if (index == -1) {
    68                 index = memberTableModel.getRowCount() - 1;
    69             }
    70             memberTableModel.addMembersAfterIdx(toAdd, index);
     35        new MemberTransferHandler().importData(getSupport());
     36    }
    7137
    72         } catch (AddAbortException ex) {
    73             Main.trace(ex);
    74         }
     38    private TransferSupport getSupport() {
     39        return new TransferSupport(memberTable, ClipboardUtils.getClipboard().getContents(null));
    7540    }
    7641
    7742    @Override
    7843    protected void updateEnabledState() {
    79         // Do nothing
     44        setEnabled(new MemberTransferHandler().canImport(getSupport()));
     45    }
     46
     47    @Override
     48    public void flavorsChanged(FlavorEvent e) {
     49        updateEnabledState();
    8050    }
    8151}
  • src/org/openstreetmap/josm/gui/download/DownloadDialog.java

    diff --git a/src/org/openstreetmap/josm/gui/download/DownloadDialog.java b/src/org/openstreetmap/josm/gui/download/DownloadDialog.java
    index c937092..b444bbd 100644
    a b import org.openstreetmap.josm.Main;  
    3838import org.openstreetmap.josm.actions.ExpertToggleAction;
    3939import org.openstreetmap.josm.data.Bounds;
    4040import org.openstreetmap.josm.gui.MapView;
     41import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
    4142import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
    4243import org.openstreetmap.josm.gui.help.HelpUtil;
    4344import org.openstreetmap.josm.gui.util.GuiHelper;
    public class DownloadDialog extends JDialog {  
    242243        getRootPane().getActionMap().put("checkClipboardContents", new AbstractAction() {
    243244            @Override
    244245            public void actionPerformed(ActionEvent e) {
    245                 String clip = Utils.getClipboardContent();
     246                String clip = ClipboardUtils.getClipboardStringContent();
    246247                if (clip == null) {
    247248                    return;
    248249                }
  • src/org/openstreetmap/josm/gui/layer/NoteLayer.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/NoteLayer.java b/src/org/openstreetmap/josm/gui/layer/NoteLayer.java
    index 90f65b0..4b9c1a8 100644
    a b import org.openstreetmap.josm.data.notes.NoteComment;  
    3030import org.openstreetmap.josm.data.osm.NoteData;
    3131import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
    3232import org.openstreetmap.josm.gui.MapView;
     33import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
    3334import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
    3435import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
    3536import org.openstreetmap.josm.gui.io.AbstractIOTask;
    import org.openstreetmap.josm.io.OsmApi;  
    4041import org.openstreetmap.josm.io.XmlWriter;
    4142import org.openstreetmap.josm.tools.ColorHelper;
    4243import org.openstreetmap.josm.tools.ImageProvider;
    43 import org.openstreetmap.josm.tools.Utils;
    4444import org.openstreetmap.josm.tools.date.DateUtils;
    4545
    4646/**
    public class NoteLayer extends AbstractModifiableLayer implements MouseListener  
    237237    public void mouseClicked(MouseEvent e) {
    238238        if (SwingUtilities.isRightMouseButton(e) && noteData.getSelectedNote() != null) {
    239239            final String url = OsmApi.getOsmApi().getBaseUrl() + "notes/" + noteData.getSelectedNote().getId();
    240             Utils.copyToClipboard(url);
     240            ClipboardUtils.copyString(url);
    241241            return;
    242242        } else if (!SwingUtilities.isLeftMouseButton(e)) {
    243243            return;
  • src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java b/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
    index 6c58bca..783b9b2 100644
    a b import org.openstreetmap.josm.gui.MapFrame.MapModeChangeListener;  
    5252import org.openstreetmap.josm.gui.MapView;
    5353import org.openstreetmap.josm.gui.NavigatableComponent;
    5454import org.openstreetmap.josm.gui.PleaseWaitRunnable;
     55import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
    5556import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
    5657import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
    5758import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
    public class GeoImageLayer extends AbstractModifiableLayer implements PropertyCh  
    724725
    725726    public void copyCurrentPhotoPath() {
    726727        if (data != null && !data.isEmpty() && currentPhoto >= 0 && currentPhoto < data.size()) {
    727             Utils.copyToClipboard(data.get(currentPhoto).getFile().toString());
     728            ClipboardUtils.copyString(data.get(currentPhoto).getFile().toString());
    728729        }
    729730    }
    730731
  • src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java

    diff --git a/src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java b/src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java
    index 53499d8..90e3efa 100644
    a b import java.beans.PropertyChangeListener;  
    77import java.beans.PropertyChangeSupport;
    88import java.util.ArrayList;
    99import java.util.Collection;
     10import java.util.Collections;
    1011import java.util.Comparator;
    1112import java.util.EnumSet;
    1213import java.util.HashMap;
    import org.openstreetmap.josm.command.SequenceCommand;  
    2425import org.openstreetmap.josm.data.osm.OsmPrimitive;
    2526import org.openstreetmap.josm.data.osm.Tag;
    2627import org.openstreetmap.josm.data.osm.TagCollection;
     28import org.openstreetmap.josm.data.osm.TagMap;
    2729import org.openstreetmap.josm.data.osm.Tagged;
    2830import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
    2931import org.openstreetmap.josm.tools.CheckParameterUtil;
    public class TagEditorModel extends AbstractTableModel {  
    4749
    4850    private transient OsmPrimitive primitive;
    4951
     52    private EndEditListener endEditListener;
     53
    5054    /**
    5155     * Creates a new tag editor model. Internally allocates two selection models
    5256     * for row selection and column selection.
    public class TagEditorModel extends AbstractTableModel {  
    166170     * removes all tags in the model
    167171     */
    168172    public void clear() {
     173        commitPendingEdit();
    169174        boolean wasEmpty = tags.isEmpty();
    170175        tags.clear();
    171176        if (!wasEmpty) {
    public class TagEditorModel extends AbstractTableModel {  
    182187     * @throws IllegalArgumentException if tag is null
    183188     */
    184189    public void add(TagModel tag) {
     190        commitPendingEdit();
    185191        CheckParameterUtil.ensureParameterNotNull(tag, "tag");
    186192        tags.add(tag);
    187193        setDirty(true);
    188194        fireTableDataChanged();
    189195    }
    190196
     197    /**
     198     * Add a tag at the beginning of the table.
     199     *
     200     * @param tag The tag to add
     201     *
     202     * @throws IllegalArgumentException if tag is null
     203     *
     204     * @see #add(TagModel)
     205     */
    191206    public void prepend(TagModel tag) {
     207        commitPendingEdit();
    192208        CheckParameterUtil.ensureParameterNotNull(tag, "tag");
    193209        tags.add(0, tag);
    194210        setDirty(true);
    public class TagEditorModel extends AbstractTableModel {  
    208224     * @param value the value; converted to "" if null
    209225     */
    210226    public void add(String name, String value) {
     227        commitPendingEdit();
    211228        String key = (name == null) ? "" : name;
    212229        String val = (value == null) ? "" : value;
    213230
    public class TagEditorModel extends AbstractTableModel {  
    258275    public void deleteTagNames(int[] tagIndices) {
    259276        if (tags == null)
    260277            return;
     278        commitPendingEdit();
    261279        for (int tagIdx : tagIndices) {
    262280            TagModel tag = tags.get(tagIdx);
    263281            if (tag != null) {
    public class TagEditorModel extends AbstractTableModel {  
    276294    public void deleteTagValues(int[] tagIndices) {
    277295        if (tags == null)
    278296            return;
     297        commitPendingEdit();
    279298        for (int tagIdx : tagIndices) {
    280299            TagModel tag = tags.get(tagIdx);
    281300            if (tag != null) {
    public class TagEditorModel extends AbstractTableModel {  
    292311     * @param name the name. Ignored if null.
    293312     */
    294313    public void delete(String name) {
     314        commitPendingEdit();
    295315        if (name == null)
    296316            return;
    297317        Iterator<TagModel> it = tags.iterator();
    public class TagEditorModel extends AbstractTableModel {  
    317337    public void deleteTags(int[] tagIndices) {
    318338        if (tags == null)
    319339            return;
     340        commitPendingEdit();
    320341        List<TagModel> toDelete = new ArrayList<>();
    321342        for (int tagIdx : tagIndices) {
    322343            TagModel tag = tags.get(tagIdx);
    public class TagEditorModel extends AbstractTableModel {  
    355376     * @param primitive the OSM primitive
    356377     */
    357378    public void initFromPrimitive(Tagged primitive) {
     379        commitPendingEdit();
    358380        this.tags.clear();
    359381        for (String key : primitive.keySet()) {
    360382            String value = primitive.get(key);
    361383            this.tags.add(new TagModel(key, value));
    362384        }
    363         TagModel tag = new TagModel();
    364385        sort();
     386        TagModel tag = new TagModel();
    365387        tags.add(tag);
    366388        setDirty(false);
    367389        fireTableDataChanged();
    public class TagEditorModel extends AbstractTableModel {  
    373395     * @param tags the tags of an OSM primitive
    374396     */
    375397    public void initFromTags(Map<String, String> tags) {
     398        commitPendingEdit();
    376399        this.tags.clear();
    377400        for (Entry<String, String> entry : tags.entrySet()) {
    378401            this.tags.add(new TagModel(entry.getKey(), entry.getValue()));
    public class TagEditorModel extends AbstractTableModel {  
    390413     * @param tags the tags
    391414     */
    392415    public void initFromTags(TagCollection tags) {
     416        commitPendingEdit();
    393417        this.tags.clear();
    394418        if (tags == null) {
    395419            setDirty(false);
    public class TagEditorModel extends AbstractTableModel {  
    423447     * @return the map of key/value pairs
    424448     */
    425449    private Map<String, String> applyToTags(boolean keepEmpty) {
    426         Map<String, String> result = new HashMap<>();
     450        // TagMap preserves the order of tags.
     451        TagMap result = new TagMap();
    427452        for (TagModel tag: this.tags) {
    428453            // tag still holds an unchanged list of different values for the same key.
    429454            // no property change command required
    public class TagEditorModel extends AbstractTableModel {  
    432457            }
    433458
    434459            // tag name holds an empty key. Don't apply it to the selection.
    435             //
    436460            if (!keepEmpty && (tag.getName().trim().isEmpty() || tag.getValue().trim().isEmpty())) {
    437461                continue;
    438462            }
    public class TagEditorModel extends AbstractTableModel {  
    538562     * sorts the current tags according alphabetical order of names
    539563     */
    540564    protected void sort() {
    541         java.util.Collections.sort(
     565        Collections.sort(
    542566                tags,
    543567                new Comparator<TagModel>() {
    544568                    @Override
    public class TagEditorModel extends AbstractTableModel {  
    590614     * @param tags - the list
    591615     */
    592616    public void updateTags(List<Tag> tags) {
    593          if (tags.isEmpty())
     617        if (tags.isEmpty())
    594618            return;
    595619
     620        commitPendingEdit();
    596621        Map<String, TagModel> modelTags = new HashMap<>();
    597622        for (int i = 0; i < getRowCount(); i++) {
    598623            TagModel tagModel = get(i);
    public class TagEditorModel extends AbstractTableModel {  
    647672        return this;
    648673    }
    649674
     675    /**
     676     * Sets the listener that is notified when an edit should be aborted.
     677     * @param endEditListener The listener to be notified when editing should be aborted.
     678     */
     679    public void setEndEditListener(EndEditListener endEditListener) {
     680        this.endEditListener = endEditListener;
     681    }
     682
     683    private void commitPendingEdit() {
     684        if (endEditListener != null) {
     685            endEditListener.endCellEditing();
     686        }
     687    }
     688
    650689    class SelectionStateMemento {
    651690        private final int rowMin;
    652691        private final int rowMax;
    public class TagEditorModel extends AbstractTableModel {  
    673712            colSelectionModel.setValueIsAdjusting(false);
    674713        }
    675714    }
     715
     716    /**
     717     * A listener that is called whenever the cells may be updated from outside the editor and the editor should thus be commited.
     718     */
     719    public interface EndEditListener {
     720        /**
     721         * Requests to end the editing of any cells on this model
     722         */
     723        public void endCellEditing();
     724    }
    676725}
  • src/org/openstreetmap/josm/gui/tagging/TagTable.java

    diff --git a/src/org/openstreetmap/josm/gui/tagging/TagTable.java b/src/org/openstreetmap/josm/gui/tagging/TagTable.java
    index cea481e..6d2899e 100644
    a b  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.gui.tagging;
    33
    4 import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
    54import static org.openstreetmap.josm.tools.I18n.tr;
    65
    76import java.awt.Component;
    import java.awt.event.ActionEvent;  
    1211import java.awt.event.KeyEvent;
    1312import java.beans.PropertyChangeEvent;
    1413import java.beans.PropertyChangeListener;
    15 import java.util.ArrayList;
    1614import java.util.Collections;
    1715import java.util.EventObject;
    18 import java.util.List;
    19 import java.util.Map;
    2016import java.util.concurrent.CopyOnWriteArrayList;
    2117
    2218import javax.swing.AbstractAction;
    import javax.swing.event.ListSelectionListener;  
    3127import javax.swing.text.JTextComponent;
    3228
    3329import org.openstreetmap.josm.Main;
    34 import org.openstreetmap.josm.actions.CopyAction;
    35 import org.openstreetmap.josm.actions.PasteTagsAction;
    36 import org.openstreetmap.josm.data.osm.OsmPrimitive;
    37 import org.openstreetmap.josm.data.osm.PrimitiveData;
    3830import org.openstreetmap.josm.data.osm.Relation;
    39 import org.openstreetmap.josm.data.osm.Tag;
     31import org.openstreetmap.josm.data.osm.TagMap;
     32import org.openstreetmap.josm.gui.datatransfer.OsmTransferHandler;
     33import org.openstreetmap.josm.gui.tagging.TagEditorModel.EndEditListener;
    4034import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
    4135import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
    4236import org.openstreetmap.josm.gui.widgets.JosmTable;
    4337import org.openstreetmap.josm.tools.ImageProvider;
    44 import org.openstreetmap.josm.tools.TextTagParser;
    45 import org.openstreetmap.josm.tools.Utils;
    4638
    4739/**
    4840 * This is the tabular editor component for OSM tags.
    4941 * @since 1762
    5042 */
    51 public class TagTable extends JosmTable {
     43public class TagTable extends JosmTable implements EndEditListener {
    5244    /** the table cell editor used by this table */
    5345    private TagCellEditor editor;
    5446    private final TagEditorModel model;
    public class TagTable extends JosmTable {  
    210202            default: // Do nothing
    211203            }
    212204
    213             if (isEditing()) {
    214                 CellEditor cEditor = getCellEditor();
    215                 if (cEditor != null) {
    216                     cEditor.cancelCellEditing();
    217                 }
    218             }
     205            endCellEditing();
    219206
    220207            if (model.getRowCount() == 0) {
    221208                model.ensureOneTag();
    public class TagTable extends JosmTable {  
    232219        }
    233220
    234221        protected final void updateEnabledState() {
    235             if (isEditing() && getSelectedColumnCount() == 1 && getSelectedRowCount() == 1) {
    236                 setEnabled(true);
    237             } else if (!isEditing() && getSelectedColumnCount() == 1 && getSelectedRowCount() == 1) {
    238                 setEnabled(true);
    239             } else if (getSelectedColumnCount() > 1 || getSelectedRowCount() > 1) {
     222            if (getSelectedColumnCount() >= 1 && getSelectedRowCount() >= 1) {
    240223                setEnabled(true);
    241224            } else {
    242225                setEnabled(false);
    public class TagTable extends JosmTable {  
    294277        public void actionPerformed(ActionEvent e) {
    295278            Relation relation = new Relation();
    296279            model.applyToPrimitive(relation);
    297 
    298             String buf = Utils.getClipboardContent();
    299             if (buf == null || buf.isEmpty() || buf.matches(CopyAction.CLIPBOARD_REGEXP)) {
    300                 List<PrimitiveData> directlyAdded = Main.pasteBuffer.getDirectlyAdded();
    301                 if (directlyAdded == null || directlyAdded.isEmpty()) return;
    302                 PasteTagsAction.TagPaster tagPaster = new PasteTagsAction.TagPaster(directlyAdded,
    303                         Collections.<OsmPrimitive>singletonList(relation));
    304                 model.updateTags(tagPaster.execute());
    305             } else {
    306                  // Paste tags from arbitrary text
    307                  Map<String, String> tags = TextTagParser.readTagsFromText(buf);
    308                  if (tags == null || tags.isEmpty()) {
    309                     TextTagParser.showBadBufferMessage(ht("/Action/PasteTags"));
    310                  } else if (TextTagParser.validateTags(tags)) {
    311                      List<Tag> newTags = new ArrayList<>();
    312                      for (Map.Entry<String, String> entry: tags.entrySet()) {
    313                         String k = entry.getKey();
    314                         String v = entry.getValue();
    315                         newTags.add(new Tag(k, v));
    316                      }
    317                      model.updateTags(newTags);
    318                  }
    319             }
     280            new OsmTransferHandler().pasteTags(Collections.singleton(relation));
     281            model.updateTags(new TagMap(relation.getKeys()).getTags());
    320282        }
    321283
    322284        protected final void updateEnabledState() {
    public class TagTable extends JosmTable {  
    414376                  .setSelectionModel(model.getColumnSelectionModel()).build(),
    415377              model.getRowSelectionModel());
    416378        this.model = model;
     379        model.setEndEditListener(this);
    417380        init(maxCharacters);
    418381    }
    419382
    public class TagTable extends JosmTable {  
    487450     * @param editor tag cell editor
    488451     */
    489452    public void setTagCellEditor(TagCellEditor editor) {
    490         if (isEditing()) {
    491             this.editor.cancelCellEditing();
    492         }
     453        endCellEditing();
    493454        this.editor = editor;
    494455        getColumnModel().getColumn(0).setCellEditor(editor);
    495456        getColumnModel().getColumn(1).setCellEditor(editor);
    public class TagTable extends JosmTable {  
    548509    }
    549510
    550511    @Override
     512    public void endCellEditing() {
     513        if (isEditing()) {
     514            CellEditor cEditor = getCellEditor();
     515            if (cEditor != null) {
     516                // First attempt to commit. If this does not work, cancel.
     517                cEditor.stopCellEditing();
     518                cEditor.cancelCellEditing();
     519            }
     520        }
     521    }
     522
     523    @Override
    551524    public void removeEditor() {
    552525        // make sure we unregister our custom implementation of CellEditorRemover
    553526        KeyboardFocusManager.getCurrentKeyboardFocusManager().
  • src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletingComboBox.java

    diff --git a/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletingComboBox.java b/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletingComboBox.java
    index c46e863..c98a0df 100644
    a b import javax.swing.text.PlainDocument;  
    2323import javax.swing.text.StyleConstants;
    2424
    2525import org.openstreetmap.josm.Main;
    26 import org.openstreetmap.josm.gui.util.GuiHelper;
     26import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
    2727import org.openstreetmap.josm.gui.widgets.JosmComboBox;
    28 import org.openstreetmap.josm.tools.Utils;
    2928
    3029/**
    3130 * Auto-completing ComboBox.
    public class AutoCompletingComboBox extends JosmComboBox<AutoCompletionListItem>  
    133132            }
    134133            final JTextComponent editorComponent = comboBox.getEditorComponent();
    135134            // save unix system selection (middle mouse paste)
    136             Clipboard sysSel = GuiHelper.getSystemSelection();
     135            Clipboard sysSel = ClipboardUtils.getSystemSelection();
    137136            if (sysSel != null) {
    138                 Transferable old = Utils.getTransferableContent(sysSel);
     137                Transferable old = ClipboardUtils.getClipboardContent(sysSel);
    139138                editorComponent.select(start, end);
    140139                if (old != null) {
    141140                    sysSel.setContents(old, null);
    public class AutoCompletingComboBox extends JosmComboBox<AutoCompletionListItem>  
    200199                            Main.map.keyDetector.setEnabled(false);
    201200                        }
    202201                        // save unix system selection (middle mouse paste)
    203                         Clipboard sysSel = GuiHelper.getSystemSelection();
     202                        Clipboard sysSel = ClipboardUtils.getSystemSelection();
    204203                        if (sysSel != null) {
    205                             Transferable old = Utils.getTransferableContent(sysSel);
     204                            Transferable old = ClipboardUtils.getClipboardContent(sysSel);
    206205                            editorComponent.selectAll();
    207206                            if (old != null) {
    208207                                sysSel.setContents(old, null);
  • src/org/openstreetmap/josm/gui/util/GuiHelper.java

    diff --git a/src/org/openstreetmap/josm/gui/util/GuiHelper.java b/src/org/openstreetmap/josm/gui/util/GuiHelper.java
    index b6612e4..b61c406 100644
    a b import java.awt.Image;  
    2020import java.awt.Stroke;
    2121import java.awt.Toolkit;
    2222import java.awt.Window;
    23 import java.awt.datatransfer.Clipboard;
    2423import java.awt.event.ActionListener;
    2524import java.awt.event.HierarchyEvent;
    2625import java.awt.event.HierarchyListener;
    public final class GuiHelper {  
    496495    }
    497496
    498497    /**
    499      * Gets the singleton instance of the system selection as a <code>Clipboard</code> object.
    500      * This allows an application to read and modify the current, system-wide selection.
    501      * @return the system selection as a <code>Clipboard</code>, or <code>null</code> if the native platform does not
    502      *         support a system selection <code>Clipboard</code> or if GraphicsEnvironment.isHeadless() returns true
    503      * @see Toolkit#getSystemSelection
    504      * @since 9576
    505      */
    506     public static Clipboard getSystemSelection() {
    507         return GraphicsEnvironment.isHeadless() ? null : Toolkit.getDefaultToolkit().getSystemSelection();
    508     }
    509 
    510     /**
    511498     * Returns the first <code>Window</code> ancestor of event source, or
    512499     * {@code null} if event source is not a component contained inside a <code>Window</code>.
    513500     * @param e event object
  • src/org/openstreetmap/josm/gui/widgets/AbstractIdTextField.java

    diff --git a/src/org/openstreetmap/josm/gui/widgets/AbstractIdTextField.java b/src/org/openstreetmap/josm/gui/widgets/AbstractIdTextField.java
    index a9d1f33..5a695ed 100644
    a b package org.openstreetmap.josm.gui.widgets;  
    44import javax.swing.text.JTextComponent;
    55
    66import org.openstreetmap.josm.Main;
     7import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
    78import org.openstreetmap.josm.tools.Utils;
    89
    910/**
    public abstract class AbstractIdTextField<T extends AbstractTextComponentValidat  
    7071     * Tries to set text from clipboard (no effect with invalid or empty clipboard)
    7172     */
    7273    public void tryToPasteFromClipboard() {
    73         tryToPasteFrom(Utils.getClipboardContent());
     74        tryToPasteFrom(ClipboardUtils.getClipboardStringContent());
    7475    }
    7576
    7677    /**
  • src/org/openstreetmap/josm/gui/widgets/UrlLabel.java

    diff --git a/src/org/openstreetmap/josm/gui/widgets/UrlLabel.java b/src/org/openstreetmap/josm/gui/widgets/UrlLabel.java
    index fbb21e4..6a5ac58 100644
    a b import java.awt.event.MouseListener;  
    1010import javax.swing.JLabel;
    1111import javax.swing.SwingUtilities;
    1212
     13import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
    1314import org.openstreetmap.josm.tools.OpenBrowser;
    14 import org.openstreetmap.josm.tools.Utils;
    1515
    1616/**
    1717 * Label that contains a clickable link.
    public class UrlLabel extends JLabel implements MouseListener {  
    111111        if (SwingUtilities.isLeftMouseButton(e)) {
    112112            OpenBrowser.displayUrl(url);
    113113        } else if (SwingUtilities.isRightMouseButton(e)) {
    114             Utils.copyToClipboard(url);
     114            ClipboardUtils.copyString(url);
    115115        }
    116116    }
    117117
  • src/org/openstreetmap/josm/tools/TextTagParser.java

    diff --git a/src/org/openstreetmap/josm/tools/TextTagParser.java b/src/org/openstreetmap/josm/tools/TextTagParser.java
    index a6aa70b..c28c092 100644
    a b import javax.swing.JPanel;  
    1818
    1919import org.openstreetmap.josm.Main;
    2020import org.openstreetmap.josm.gui.ExtendedDialog;
     21import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
    2122import org.openstreetmap.josm.gui.help.HelpUtil;
    2223import org.openstreetmap.josm.gui.widgets.UrlLabel;
    2324import org.openstreetmap.josm.io.XmlWriter;
    public final class TextTagParser {  
    290291        int r = ed.getValue();
    291292        if (r == 0) r = 2;
    292293        // clean clipboard if user asked
    293         if (r == 3) Utils.copyToClipboard("");
     294        if (r == 3) ClipboardUtils.copyString("");
    294295        return r;
    295296    }
    296297
    public final class TextTagParser {  
    325326
    326327        int r = ed.getValue();
    327328        // clean clipboard if user asked
    328         if (r == 2) Utils.copyToClipboard("");
     329        if (r == 2) ClipboardUtils.copyString("");
    329330    }
    330331}
  • src/org/openstreetmap/josm/tools/Utils.java

    diff --git a/src/org/openstreetmap/josm/tools/Utils.java b/src/org/openstreetmap/josm/tools/Utils.java
    index bc151aa..ef320a9 100644
    a b import static org.openstreetmap.josm.tools.I18n.trn;  
    77
    88import java.awt.Color;
    99import java.awt.Font;
    10 import java.awt.HeadlessException;
    11 import java.awt.Toolkit;
    1210import java.awt.datatransfer.Clipboard;
    13 import java.awt.datatransfer.ClipboardOwner;
    14 import java.awt.datatransfer.DataFlavor;
    15 import java.awt.datatransfer.StringSelection;
    1611import java.awt.datatransfer.Transferable;
    17 import java.awt.datatransfer.UnsupportedFlavorException;
    1812import java.awt.font.FontRenderContext;
    1913import java.awt.font.GlyphVector;
    2014import java.io.BufferedReader;
    import javax.xml.parsers.SAXParserFactory;  
    7165
    7266import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
    7367import org.openstreetmap.josm.Main;
     68import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
    7469import org.w3c.dom.Document;
    7570import org.xml.sax.InputSource;
    7671import org.xml.sax.SAXException;
    public final class Utils {  
    623618     * Copies the string {@code s} to system clipboard.
    624619     * @param s string to be copied to clipboard.
    625620     * @return true if succeeded, false otherwise.
     621     * @deprecated Use {@link ClipboardUtils#copyString(String)}. To be removed end of 2016.
    626622     */
     623    @Deprecated
    627624    public static boolean copyToClipboard(String s) {
    628         try {
    629             Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(s), new ClipboardOwner() {
    630                 @Override
    631                 public void lostOwnership(Clipboard clpbrd, Transferable t) {
    632                     // Do nothing
    633                 }
    634             });
    635             return true;
    636         } catch (IllegalStateException | HeadlessException ex) {
    637             Main.error(ex);
    638             return false;
    639         }
     625        return ClipboardUtils.copyString(s);
    640626    }
    641627
    642628    /**
    public final class Utils {  
    644630     * @param clipboard clipboard from which contents are retrieved
    645631     * @return clipboard contents if available, {@code null} otherwise.
    646632     * @since 8429
     633     * @deprecated Use {@link ClipboardUtils#getClipboardContent(Clipboard)} instead. To be removed end of 2016.
    647634     */
     635    @Deprecated
    648636    public static Transferable getTransferableContent(Clipboard clipboard) {
    649         Transferable t = null;
    650         for (int tries = 0; t == null && tries < 10; tries++) {
    651             try {
    652                 t = clipboard.getContents(null);
    653             } catch (IllegalStateException e) {
    654                 // Clipboard currently unavailable.
    655                 // On some platforms, the system clipboard is unavailable while it is accessed by another application.
    656                 try {
    657                     Thread.sleep(1);
    658                 } catch (InterruptedException ex) {
    659                     Main.warn("InterruptedException in "+Utils.class.getSimpleName()+" while getting clipboard content");
    660                 }
    661             } catch (NullPointerException e) {
    662                 // JDK-6322854: On Linux/X11, NPE can happen for unknown reasons, on all versions of Java
    663                 Main.error(e);
    664             }
    665         }
    666         return t;
     637        return ClipboardUtils.getClipboardContent(clipboard);
    667638    }
    668639
    669640    /**
    670641     * Extracts clipboard content as string.
    671642     * @return string clipboard contents if available, {@code null} otherwise.
     643     * @deprecated Use {@link ClipboardUtils#getClipboardStringContent()}. To be removed end of 2016
    672644     */
     645    @Deprecated
    673646    public static String getClipboardContent() {
    674         try {
    675             Transferable t = getTransferableContent(Toolkit.getDefaultToolkit().getSystemClipboard());
    676             if (t != null && t.isDataFlavorSupported(DataFlavor.stringFlavor)) {
    677                 return (String) t.getTransferData(DataFlavor.stringFlavor);
    678             }
    679         } catch (UnsupportedFlavorException | IOException | HeadlessException ex) {
    680             Main.error(ex);
    681             return null;
    682         }
    683         return null;
     647        return ClipboardUtils.getClipboardStringContent();
    684648    }
    685649
    686650    /**
  • src/org/openstreetmap/josm/tools/bugreport/DebugTextDisplay.java

    diff --git a/src/org/openstreetmap/josm/tools/bugreport/DebugTextDisplay.java b/src/org/openstreetmap/josm/tools/bugreport/DebugTextDisplay.java
    index 28f88ac..237b952 100644
    a b import java.awt.Dimension;  
    55
    66import javax.swing.JScrollPane;
    77
     8import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
    89import org.openstreetmap.josm.gui.widgets.JosmTextArea;
    910import org.openstreetmap.josm.tools.Utils;
    1011
    public class DebugTextDisplay extends JScrollPane {  
    3435     * @return <code>true</code> if copy was successful
    3536     */
    3637    public boolean copyToClippboard() {
    37         return Utils.copyToClipboard(text);
     38        return ClipboardUtils.copyString(text);
    3839    }
    3940}
  • test/unit/org/openstreetmap/josm/actions/CopyActionTest.java

    diff --git a/test/unit/org/openstreetmap/josm/actions/CopyActionTest.java b/test/unit/org/openstreetmap/josm/actions/CopyActionTest.java
    index 426b4e7..05758b9 100644
    a b  
    22package org.openstreetmap.josm.actions;
    33
    44import static org.junit.Assert.assertEquals;
     5import static org.junit.Assert.assertFalse;
     6import static org.junit.Assert.assertNotNull;
     7import static org.junit.Assert.assertTrue;
     8import static org.junit.Assert.fail;
    59
     10import java.awt.datatransfer.Clipboard;
     11import java.awt.datatransfer.DataFlavor;
     12import java.awt.datatransfer.StringSelection;
     13import java.awt.datatransfer.UnsupportedFlavorException;
     14import java.io.IOException;
    615import java.util.Arrays;
    7 import java.util.Collections;
    816
    9 import org.junit.BeforeClass;
     17import org.junit.Rule;
    1018import org.junit.Test;
    11 import org.openstreetmap.josm.JOSMFixture;
    12 import org.openstreetmap.josm.data.osm.Relation;
     19import org.openstreetmap.josm.Main;
     20import org.openstreetmap.josm.data.coor.LatLon;
     21import org.openstreetmap.josm.data.osm.DataSet;
     22import org.openstreetmap.josm.data.osm.Node;
    1323import org.openstreetmap.josm.data.osm.Way;
     24import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
     25import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData;
     26import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     27import org.openstreetmap.josm.testutils.JOSMTestRules;
     28
     29import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    1430
    1531/**
    1632 * Unit tests for class {@link CopyAction}.
    1733 */
    1834public class CopyActionTest {
     35    private final class CapturingCopyAction extends CopyAction {
     36        private boolean warningShown;
     37
     38        @Override
     39        protected void showEmptySelectionWarning() {
     40            warningShown = true;
     41        }
     42    }
    1943
    2044    /**
    21      * Setup test.
     45     * We need prefs for this.
    2246     */
    23     @BeforeClass
    24     public static void setUpBeforeClass() {
    25         JOSMFixture.createUnitTestFixture().init();
    26     }
     47    @Rule
     48    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
     49    public JOSMTestRules test = new JOSMTestRules().preferences().platform().fakeAPI();
    2750
    2851    /**
    29      * Test of {@link CopyAction#getCopyString} method for a single way.
     52     * Test that copy action copies the selected primitive
     53     * @throws IOException
     54     * @throws UnsupportedFlavorException
    3055     */
    3156    @Test
    32     public void testCopyStringWay() {
    33         final Way way = new Way(123L);
    34         assertEquals("way 123", CopyAction.getCopyString(Collections.singleton(way)));
    35     }
     57    public void testWarnOnEmpty() throws UnsupportedFlavorException, IOException {
     58        Clipboard clippboard = ClipboardUtils.getClipboard();
     59        clippboard.setContents(new StringSelection("test"), null);
     60
     61        CapturingCopyAction action = new CapturingCopyAction();
     62
     63        action.updateEnabledState();
     64        assertFalse(action.isEnabled());
     65        action.actionPerformed(null);
     66        assertTrue(action.warningShown);
     67
     68        Main.getLayerManager().addLayer(new OsmDataLayer(new DataSet(), "test", null));
     69        action.warningShown = false;
    3670
     71        action.updateEnabledState();
     72        assertFalse(action.isEnabled());
     73        action.actionPerformed(null);
     74        assertTrue(action.warningShown);
     75
     76        assertEquals("test", clippboard.getContents(null).getTransferData(DataFlavor.stringFlavor));
     77    }
    3778    /**
    38      * Test of {@link CopyAction#getCopyString} method for a way and a relation.
     79     * Test that copy action copies the selected primitive
     80     * @throws IOException
     81     * @throws UnsupportedFlavorException
    3982     */
    4083    @Test
    41     public void testCopyStringWayRelation() {
    42         final Way way = new Way(123L);
    43         final Relation relation = new Relation(456);
    44         assertEquals("way 123,relation 456", CopyAction.getCopyString(Arrays.asList(way, relation)));
    45         assertEquals("relation 456,way 123", CopyAction.getCopyString(Arrays.asList(relation, way)));
     84    public void testCopySinglePrimitive() throws UnsupportedFlavorException, IOException {
     85        DataSet data = new DataSet();
     86
     87        Node node1 = new Node();
     88        node1.setCoor(LatLon.ZERO);
     89        data.addPrimitive(node1);
     90
     91        Node node2 = new Node();
     92        node2.setCoor(LatLon.ZERO);
     93        data.addPrimitive(node2);
     94        Way way = new Way();
     95        way.setNodes(Arrays.asList(node1, node2));
     96        data.addPrimitive(way);
     97        data.setSelected(way);
     98
     99        Main.getLayerManager().addLayer(new OsmDataLayer(data, "test", null));
     100
     101        CopyAction action = new CopyAction() {
     102            @Override
     103            protected void showEmptySelectionWarning() {
     104                fail("Selection is not empty.");
     105            }
     106        };
     107        action.updateEnabledState();
     108        assertTrue(action.isEnabled());
     109        action.actionPerformed(null);
     110
     111        Object copied = ClipboardUtils.getClipboard().getContents(null).getTransferData(PrimitiveTransferData.DATA_FLAVOR);
     112        assertNotNull(copied);
     113        assertTrue(copied instanceof PrimitiveTransferData);
     114        PrimitiveTransferData ptd = (PrimitiveTransferData) copied;
     115        Object[] direct = ptd.getDirectlyAdded().toArray();
     116        assertEquals(1, direct.length);
     117        Object[] referenced = ptd.getReferenced().toArray();
     118        assertEquals(2, referenced.length);
    46119    }
    47120}
  • new file test/unit/org/openstreetmap/josm/gui/datatransfer/ClipboardUtilsTest.java

    diff --git a/test/unit/org/openstreetmap/josm/gui/datatransfer/ClipboardUtilsTest.java b/test/unit/org/openstreetmap/josm/gui/datatransfer/ClipboardUtilsTest.java
    new file mode 100644
    index 0000000..fc4be5a
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.datatransfer;
     3
     4import static org.junit.Assert.assertEquals;
     5import static org.junit.Assert.assertNotNull;
     6import static org.junit.Assert.assertNull;
     7import static org.junit.Assert.assertSame;
     8import static org.junit.Assert.assertTrue;
     9
     10import java.awt.GraphicsEnvironment;
     11import java.awt.datatransfer.Clipboard;
     12import java.awt.datatransfer.DataFlavor;
     13import java.awt.datatransfer.StringSelection;
     14import java.awt.datatransfer.Transferable;
     15import java.awt.datatransfer.UnsupportedFlavorException;
     16import java.io.IOException;
     17
     18import org.junit.Rule;
     19import org.junit.Test;
     20import org.openstreetmap.josm.testutils.JOSMTestRules;
     21
     22import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
     23
     24/**
     25 * Basic tests for the clipboard utils class.
     26 * @author Michael Zangl
     27 * @since xxx
     28 */
     29public class ClipboardUtilsTest {
     30    private final static class ThrowIllegalStateClipboard extends Clipboard {
     31        private int failingAccesses = 3;
     32
     33        private ThrowIllegalStateClipboard(String name) {
     34            super(name);
     35        }
     36
     37        @Override
     38        public synchronized Transferable getContents(Object requestor) {
     39            if (failingAccesses >= 0) {
     40                failingAccesses--;
     41                throw new IllegalStateException();
     42            }
     43            return super.getContents(requestor);
     44        }
     45
     46        protected synchronized void setFailingAccesses(int failingAccesses) {
     47            this.failingAccesses = failingAccesses;
     48        }
     49    }
     50
     51    private final static class SupportNothingTransferable implements Transferable {
     52        @Override
     53        public boolean isDataFlavorSupported(DataFlavor flavor) {
     54            return false;
     55        }
     56
     57        @Override
     58        public DataFlavor[] getTransferDataFlavors() {
     59            return new DataFlavor[0];
     60        }
     61
     62        @Override
     63        public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
     64            throw new UnsupportedFlavorException(flavor);
     65        }
     66    }
     67
     68    /**
     69     * No dependencies
     70     */
     71    @Rule
     72    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
     73    public JOSMTestRules test = new JOSMTestRules();
     74
     75    /**
     76     * Test {@link ClipboardUtils#getClipboard()}
     77     */
     78    @Test
     79    public void testGetClipboard() {
     80        Clipboard c = ClipboardUtils.getClipboard();
     81        assertNotNull(c);
     82        assertSame(c, ClipboardUtils.getClipboard());
     83    }
     84
     85    /**
     86     * Test {@link ClipboardUtils#copyString(String)} and {@link ClipboardUtils#getClipboardStringContent()}
     87     */
     88    @Test
     89    public void testCopyPasteString() {
     90        ClipboardUtils.copyString("");
     91        assertEquals("", ClipboardUtils.getClipboardStringContent());
     92        ClipboardUtils.copyString("xxx\nx");
     93        assertEquals("xxx\nx", ClipboardUtils.getClipboardStringContent());
     94
     95        ClipboardUtils.copy(new SupportNothingTransferable());
     96        assertEquals(null, ClipboardUtils.getClipboardStringContent());
     97    }
     98
     99    /**
     100     * Test that {@link ClipboardUtils#getClipboardContent(Clipboard)} handles illegal state exceptions
     101     */
     102    @Test
     103    public void testGetContentIllegalState() {
     104        ThrowIllegalStateClipboard throwingClipboard = new ThrowIllegalStateClipboard("test");
     105
     106        throwingClipboard.setContents(new StringSelection(""), null);
     107        Transferable content = ClipboardUtils.getClipboardContent(throwingClipboard);
     108        assertTrue(content.isDataFlavorSupported(DataFlavor.stringFlavor));
     109
     110        throwingClipboard.setFailingAccesses(50);
     111        content = ClipboardUtils.getClipboardContent(new ThrowIllegalStateClipboard("test"));
     112        assertNull(content);
     113    }
     114
     115    /**
     116     * Test that {@link ClipboardUtils#getSystemSelection()} works in headless mode.
     117     */
     118    @Test
     119    public void testSystemSelectionDoesNotFail() {
     120        assertTrue(GraphicsEnvironment.isHeadless());
     121        assertNull(ClipboardUtils.getSystemSelection());
     122    }
     123}
  • test/unit/org/openstreetmap/josm/gui/datatransfer/PrimitiveTransferableTest.java

    diff --git a/test/unit/org/openstreetmap/josm/gui/datatransfer/PrimitiveTransferableTest.java b/test/unit/org/openstreetmap/josm/gui/datatransfer/PrimitiveTransferableTest.java
    index 69ad859..2c708ca 100644
    a b package org.openstreetmap.josm.gui.datatransfer;  
    44import static org.junit.Assert.assertEquals;
    55import static org.junit.Assert.assertFalse;
    66import static org.junit.Assert.assertTrue;
    7 import static org.openstreetmap.josm.gui.datatransfer.PrimitiveTransferable.PRIMITIVE_DATA;
    87
    98import java.awt.datatransfer.DataFlavor;
    109import java.awt.datatransfer.UnsupportedFlavorException;
     10import java.util.Arrays;
    1111import java.util.Collection;
    1212import java.util.Collections;
     13import java.util.List;
    1314
    14 import org.junit.BeforeClass;
     15import org.junit.Rule;
    1516import org.junit.Test;
    16 import org.openstreetmap.josm.JOSMFixture;
    1717import org.openstreetmap.josm.data.osm.Node;
     18import org.openstreetmap.josm.data.osm.NodeData;
    1819import org.openstreetmap.josm.data.osm.PrimitiveData;
     20import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData;
     21import org.openstreetmap.josm.gui.datatransfer.data.TagTransferData;
     22import org.openstreetmap.josm.testutils.JOSMTestRules;
     23
     24import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    1925
    2026/**
    2127 * Unit tests of {@link PrimitiveTransferable} class.
    2228 */
    2329public class PrimitiveTransferableTest {
    24 
    2530    /**
    26      * Setup tests
     31     * Prefs to use OSM primitives
    2732     */
    28     @BeforeClass
    29     public static void setUpBeforeClass() {
    30         JOSMFixture.createUnitTestFixture().init();
    31     }
     33    @Rule
     34    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
     35    public JOSMTestRules test = new JOSMTestRules().preferences();
    3236
    3337    /**
    34      * Test of {@link PrimitiveTransferable#getTransferDataFlavors()} method.
     38     * Test of {@link PrimitiveTransferable#getTransferDataFlavors()} method response order
    3539     */
    3640    @Test
    3741    public void testGetTransferDataFlavors() {
    38         DataFlavor[] flavors = new PrimitiveTransferable(null).getTransferDataFlavors();
    39         assertEquals(2, flavors.length);
    40         assertEquals(PRIMITIVE_DATA, flavors[0]);
    41         assertEquals(DataFlavor.stringFlavor, flavors[1]);
     42        List<DataFlavor> flavors = Arrays.asList(new PrimitiveTransferable(null).getTransferDataFlavors());
     43        int ptd = flavors.indexOf(PrimitiveTransferData.DATA_FLAVOR);
     44        int tags = flavors.indexOf(TagTransferData.FLAVOR);
     45        int string = flavors.indexOf(DataFlavor.stringFlavor);
     46
     47        assertTrue(ptd >= 0);
     48        assertTrue(tags >= 0);
     49        assertTrue(string >= 0);
     50
     51        assertTrue(ptd < tags);
     52        assertTrue(tags < string);
    4253    }
    4354
    4455    /**
    public class PrimitiveTransferableTest {  
    4657     */
    4758    @Test
    4859    public void testIsDataFlavorSupported() {
    49         assertTrue(new PrimitiveTransferable(null).isDataFlavorSupported(PRIMITIVE_DATA));
    50         assertFalse(new PrimitiveTransferable(null).isDataFlavorSupported(null));
     60        assertTrue(new PrimitiveTransferable(null).isDataFlavorSupported(PrimitiveTransferData.DATA_FLAVOR));
     61        assertFalse(new PrimitiveTransferable(null).isDataFlavorSupported(DataFlavor.imageFlavor));
    5162    }
    5263
    5364    /**
    public class PrimitiveTransferableTest {  
    5667     */
    5768    @Test
    5869    public void testGetTransferDataNominal() throws UnsupportedFlavorException {
    59         PrimitiveTransferable pt = new PrimitiveTransferable(Collections.singleton(new Node(1)));
    60         assertEquals("node 1 # incomplete\n", pt.getTransferData(DataFlavor.stringFlavor));
    61         Collection<PrimitiveData> td = ((PrimitiveTransferable.Data) pt.getTransferData(PRIMITIVE_DATA)).getPrimitiveData();
     70        PrimitiveTransferData data = PrimitiveTransferData.getData(Collections.singleton(new Node(1)));
     71        PrimitiveTransferable pt = new PrimitiveTransferable(data);
     72        assertEquals("node 1", pt.getTransferData(DataFlavor.stringFlavor));
     73        Collection<PrimitiveData> td = ((PrimitiveTransferData) pt.getTransferData(PrimitiveTransferData.DATA_FLAVOR)).getAll();
    6274        assertEquals(1, td.size());
    63         assertTrue(td.iterator().next() instanceof PrimitiveData);
     75        assertTrue(td.iterator().next() instanceof NodeData);
     76
     77
     78        data = PrimitiveTransferData.getData(Arrays.asList(new Node(1), new Node(2)));
     79        pt = new PrimitiveTransferable(data);
     80        assertEquals("node 1\nnode 2", pt.getTransferData(DataFlavor.stringFlavor));
    6481    }
    6582
    6683    /**
    public class PrimitiveTransferableTest {  
    6986     */
    7087    @Test(expected = UnsupportedFlavorException.class)
    7188    public void testGetTransferDataError() throws UnsupportedFlavorException {
    72         new PrimitiveTransferable(Collections.singleton(new Node(1))).getTransferData(null);
     89        PrimitiveTransferData data = PrimitiveTransferData.getData(Collections.singleton(new Node(1)));
     90        new PrimitiveTransferable(data).getTransferData(DataFlavor.imageFlavor);
    7391    }
    7492}
  • test/unit/org/openstreetmap/josm/gui/datatransfer/RelationMemberTransferableTest.java

    diff --git a/test/unit/org/openstreetmap/josm/gui/datatransfer/RelationMemberTransferableTest.java b/test/unit/org/openstreetmap/josm/gui/datatransfer/RelationMemberTransferableTest.java
    index 477fbf0..1fa90e5 100644
    a b public class RelationMemberTransferableTest {  
    3737     */
    3838    @Test
    3939    public void testGetTransferDataFlavors() {
    40         DataFlavor[] flavors = new RelationMemberTransferable(null).getTransferDataFlavors();
     40        DataFlavor[] flavors = new RelationMemberTransferable(Collections.<RelationMember>emptyList()).getTransferDataFlavors();
    4141        assertEquals(2, flavors.length);
    4242        assertEquals(RELATION_MEMBER_DATA, flavors[0]);
    4343        assertEquals(DataFlavor.stringFlavor, flavors[1]);
    public class RelationMemberTransferableTest {  
    4848     */
    4949    @Test
    5050    public void testIsDataFlavorSupported() {
    51         assertTrue(new RelationMemberTransferable(null).isDataFlavorSupported(RELATION_MEMBER_DATA));
    52         assertFalse(new RelationMemberTransferable(null).isDataFlavorSupported(null));
     51        RelationMemberTransferable transferable = new RelationMemberTransferable(Collections.<RelationMember>emptyList());
     52        assertTrue(transferable.isDataFlavorSupported(RELATION_MEMBER_DATA));
     53        assertFalse(transferable.isDataFlavorSupported(null));
    5354    }
    5455
    5556    /**
  • new file test/unit/org/openstreetmap/josm/gui/dialogs/relation/actions/AbstractRelationEditorActionTest.java

    diff --git a/test/unit/org/openstreetmap/josm/gui/dialogs/relation/actions/AbstractRelationEditorActionTest.java b/test/unit/org/openstreetmap/josm/gui/dialogs/relation/actions/AbstractRelationEditorActionTest.java
    new file mode 100644
    index 0000000..cf48256
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.dialogs.relation.actions;
     3
     4import java.util.Collection;
     5import java.util.Collections;
     6import java.util.List;
     7
     8import org.junit.Before;
     9import org.junit.Rule;
     10import org.openstreetmap.josm.data.osm.DataSet;
     11import org.openstreetmap.josm.data.osm.OsmPrimitive;
     12import org.openstreetmap.josm.data.osm.Relation;
     13import org.openstreetmap.josm.data.osm.Tag;
     14import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditorTest;
     15import org.openstreetmap.josm.gui.dialogs.relation.IRelationEditor;
     16import org.openstreetmap.josm.gui.dialogs.relation.MemberTable;
     17import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel;
     18import org.openstreetmap.josm.gui.dialogs.relation.SelectionTableModel;
     19import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     20import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
     21import org.openstreetmap.josm.testutils.JOSMTestRules;
     22
     23import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
     24
     25/**
     26 * This class provides the basic test environment for relation editor actions.
     27 * @author Michael Zangl
     28 * @since xxx
     29 */
     30public abstract class AbstractRelationEditorActionTest {
     31    /**
     32     * Plattform for tooltips.
     33     */
     34    @Rule
     35    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
     36    public JOSMTestRules test = new JOSMTestRules().preferences().platform();
     37
     38    protected SelectionTableModel selectionTableModel;
     39
     40    protected IRelationEditor editor;
     41
     42    protected MemberTable memberTable;
     43
     44    protected OsmDataLayer layer;
     45
     46    protected MemberTableModel memberTableModel;
     47
     48    /**
     49     * Set up the test data required for common tests using one relation.
     50     */
     51    @Before
     52    public void setupTestData() {
     53        DataSet ds = new DataSet();
     54        final Relation orig = new Relation(1);
     55        ds.addPrimitive(orig);
     56        layer = new OsmDataLayer(ds, "test", null);
     57        memberTableModel = new MemberTableModel(orig, layer, new TaggingPresetHandler() {
     58            @Override
     59            public void updateTags(List<Tag> tags) {
     60            }
     61
     62            @Override
     63            public Collection<OsmPrimitive> getSelection() {
     64                return Collections.<OsmPrimitive>singleton(orig);
     65            }
     66        });
     67        selectionTableModel = new SelectionTableModel(layer);
     68
     69        editor = GenericRelationEditorTest.newRelationEditor(orig, layer);
     70
     71        memberTable = new MemberTable(layer, editor.getRelation(), memberTableModel);
     72    }
     73}
  • new file test/unit/org/openstreetmap/josm/gui/dialogs/relation/actions/PasteMembersActionTest.java

    diff --git a/test/unit/org/openstreetmap/josm/gui/dialogs/relation/actions/PasteMembersActionTest.java b/test/unit/org/openstreetmap/josm/gui/dialogs/relation/actions/PasteMembersActionTest.java
    new file mode 100644
    index 0000000..40e9156
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.dialogs.relation.actions;
     3
     4import static org.junit.Assert.assertEquals;
     5import static org.junit.Assert.assertFalse;
     6import static org.junit.Assert.assertSame;
     7import static org.junit.Assert.assertTrue;
     8
     9import java.util.Collections;
     10import java.util.Set;
     11
     12import org.junit.Test;
     13import org.openstreetmap.josm.data.osm.Node;
     14import org.openstreetmap.josm.data.osm.Relation;
     15import org.openstreetmap.josm.data.osm.RelationMember;
     16import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
     17import org.openstreetmap.josm.gui.datatransfer.PrimitiveTransferable;
     18import org.openstreetmap.josm.gui.datatransfer.RelationMemberTransferable;
     19import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData;
     20import org.openstreetmap.josm.gui.util.GuiHelper;
     21
     22/**
     23 * Test for {@link PasteMembersAction}
     24 * @author Michael Zangl
     25 * @since xxx
     26 */
     27public class PasteMembersActionTest extends AbstractRelationEditorActionTest {
     28    /**
     29     * Test {@link PasteMembersAction#isEnabled()}
     30     */
     31    @Test
     32    public void testEnabledState() {
     33        copyString();
     34
     35        PasteMembersAction action = new PasteMembersAction(memberTable, layer, editor);
     36        ClipboardUtils.getClipboard().addFlavorListener(action);
     37
     38        try {
     39            assertFalse(action.isEnabled());
     40
     41            Node node = new Node();
     42            copyNode(node);
     43            syncListener();
     44            assertTrue(action.isEnabled());
     45
     46            copyMember(node);
     47            syncListener();
     48            assertTrue(action.isEnabled());
     49
     50            copyString();
     51            syncListener();
     52            assertFalse(action.isEnabled());
     53        } finally {
     54            ClipboardUtils.getClipboard().removeFlavorListener(action);
     55        }
     56    }
     57
     58    private void syncListener() {
     59        GuiHelper.runInEDTAndWait(new Runnable() {
     60            @Override
     61            public void run() {
     62                // nop
     63            }
     64        });
     65    }
     66
     67    /**
     68     * Test that pasting produces the result required
     69     */
     70    @Test
     71    public void testActionWrongClipboard() {
     72        copyString();
     73        PasteMembersAction action = new PasteMembersAction(memberTable, layer, editor);
     74        action.actionPerformed(null);
     75
     76        Relation relation = new Relation(1);
     77        memberTableModel.applyToRelation(relation);
     78        assertEquals(0, relation.getMembersCount());
     79    }
     80
     81    /**
     82     * Test that pasting produces the result required
     83     */
     84    @Test
     85    public void testActionForMembers() {
     86        Node testNode = new Node(10);
     87        layer.data.addPrimitive(testNode);
     88        copyMember(testNode);
     89        PasteMembersAction action = new PasteMembersAction(memberTable, layer, editor);
     90        action.actionPerformed(null);
     91
     92        Relation relation = new Relation(1);
     93        memberTableModel.applyToRelation(relation);
     94        assertEquals(1, relation.getMembersCount());
     95        assertEquals("test", relation.getMember(0).getRole());
     96        assertSame(testNode, relation.getMember(0).getMember());
     97    }
     98
     99    /**
     100     * Test that pasting primitvies produces the result required
     101     */
     102    @Test
     103    public void testActionForPrimitives() {
     104        Node testNode = new Node(10);
     105        layer.data.addPrimitive(testNode);
     106        copyNode(testNode);
     107        PasteMembersAction action = new PasteMembersAction(memberTable, layer, editor);
     108        action.actionPerformed(null);
     109
     110        Relation relation = new Relation(1);
     111        memberTableModel.applyToRelation(relation);
     112        assertEquals(1, relation.getMembersCount());
     113        assertEquals("", relation.getMember(0).getRole());
     114        assertSame(testNode, relation.getMember(0).getMember());
     115    }
     116
     117    private void copyNode(Node node) {
     118        PrimitiveTransferData data = PrimitiveTransferData.getData(Collections.singleton(node));
     119        ClipboardUtils.copy(new PrimitiveTransferable(data));
     120    }
     121
     122    private void copyMember(Node node) {
     123        Set<RelationMember> members = Collections.singleton(new RelationMember("test", node));
     124        ClipboardUtils.copy(new RelationMemberTransferable(members));
     125    }
     126
     127    private void copyString() {
     128        ClipboardUtils.copyString("");
     129    }
     130}
  • test/unit/org/openstreetmap/josm/gui/dialogs/relation/actions/RelationEditorActionsTest.java

    diff --git a/test/unit/org/openstreetmap/josm/gui/dialogs/relation/actions/RelationEditorActionsTest.java b/test/unit/org/openstreetmap/josm/gui/dialogs/relation/actions/RelationEditorActionsTest.java
    index f44f7fb..bc5a1ab 100644
    a b  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.gui.dialogs.relation.actions;
    33
    4 import org.junit.BeforeClass;
     4import org.junit.Rule;
    55import org.junit.Test;
    6 import org.openstreetmap.josm.JOSMFixture;
    76import org.openstreetmap.josm.data.osm.DataSet;
    87import org.openstreetmap.josm.data.osm.Relation;
    98import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditorTest;
    import org.openstreetmap.josm.gui.dialogs.relation.SelectionTableModel;  
    1413import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    1514import org.openstreetmap.josm.gui.tagging.TagEditorModel;
    1615import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
     16import org.openstreetmap.josm.testutils.JOSMTestRules;
     17
     18import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    1719
    1820/**
    1921 * Unit tests for relation editor actions.
    2022 */
    2123public class RelationEditorActionsTest {
    22 
    2324    /**
    24      * Setup test.
     25     * Plattform for tooltips.
    2526     */
    26     @BeforeClass
    27     public static void setUpBeforeClass() {
    28         JOSMFixture.createUnitTestFixture().init(true);
    29     }
     27    @Rule
     28    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
     29    public JOSMTestRules test = new JOSMTestRules().preferences().platform().commands();
    3030
    3131    /**
    32      * Test all actions with minimal data.
     32     * Check that all actions do not crash.
    3333     */
    3434    @Test
    3535    public void testAllActions() {
    public class RelationEditorActionsTest {  
    5757        new CancelAction(memberTable, memberTableModel, tagModel, layer, editor, tfRole).actionPerformed(null);
    5858
    5959        new CopyMembersAction(memberTableModel, layer, editor).actionPerformed(null);
    60         new PasteMembersAction(memberTableModel, layer, editor).actionPerformed(null);
     60        new PasteMembersAction(memberTable, layer, editor).actionPerformed(null);
    6161
    6262        new DeleteCurrentRelationAction(layer, editor).actionPerformed(null);
    6363
  • test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java

    diff --git a/test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java b/test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java
    index 9fba4cd..9b8abb7 100644
    a b import org.junit.rules.Timeout;  
    1212import org.junit.runner.Description;
    1313import org.junit.runners.model.InitializationError;
    1414import org.junit.runners.model.Statement;
     15import org.openstreetmap.josm.JOSMFixture;
    1516import org.openstreetmap.josm.Main;
    1617import org.openstreetmap.josm.data.projection.Projections;
    1718import org.openstreetmap.josm.gui.util.GuiHelper;
    public class JOSMTestRules implements TestRule {  
    3940    private String i18n = null;
    4041    private boolean platform;
    4142    private boolean useProjection;
     43    private boolean commands;
    4244
    4345    /**
    4446     * Disable the default timeout for this test. Use with care.
    public class JOSMTestRules implements TestRule {  
    133135        return this;
    134136    }
    135137
     138    /**
     139     * Allow the execution of commands using {@link Main#undoRedo}
     140     * @return this instance, for easy chaining
     141     */
     142    public JOSMTestRules commands() {
     143        commands = true;
     144        return this;
     145    }
     146
    136147    @Override
    137148    public Statement apply(final Statement base, Description description) {
    138149        Statement statement = new Statement() {
    public class JOSMTestRules implements TestRule {  
    160171     * @throws InitializationError If an error occured while creating the required environment.
    161172     */
    162173    protected void before() throws InitializationError {
    163         cleanUpFromJosmFixture();
    164 
    165174        // Tests are running headless by default.
    166175        System.setProperty("java.awt.headless", "true");
     176
     177        cleanUpFromJosmFixture();
     178
    167179        // All tests use the same timezone.
    168180        TimeZone.setDefault(DateUtils.UTC);
    169181        // Set log level to info
    public class JOSMTestRules implements TestRule {  
    218230        if (platform) {
    219231            Main.determinePlatformHook();
    220232        }
     233
     234        if (commands) {
     235            // TODO: Implement a more slective version of this once Main is restructured.
     236            JOSMFixture.createUnitTestFixture().init(true);
     237        }
    221238    }
    222239
    223240    /**