001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs.layer;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.datatransfer.Transferable;
007import java.awt.datatransfer.UnsupportedFlavorException;
008import java.io.IOException;
009import java.util.ArrayList;
010import java.util.Collection;
011import java.util.List;
012import java.util.stream.Collectors;
013
014import javax.swing.JComponent;
015import javax.swing.JTable;
016import javax.swing.TransferHandler;
017
018import org.openstreetmap.josm.data.osm.DataSet;
019import org.openstreetmap.josm.gui.datatransfer.LayerTransferable;
020import org.openstreetmap.josm.gui.dialogs.LayerListDialog.LayerListModel;
021import org.openstreetmap.josm.gui.layer.Layer;
022import org.openstreetmap.josm.gui.layer.OsmDataLayer;
023import org.openstreetmap.josm.tools.Logging;
024
025/**
026 * This class allows the user to transfer layers using drag+drop.
027 * <p>
028 * It supports copy (duplication) of layers, simple moves and linking layers to a new layer manager.
029 *
030 * @author Michael Zangl
031 * @since 10605
032 */
033public class LayerListTransferHandler extends TransferHandler {
034    @Override
035    public int getSourceActions(JComponent c) {
036        if (c instanceof JTable) {
037            LayerListModel tableModel = (LayerListModel) ((JTable) c).getModel();
038            if (!tableModel.getSelectedLayers().isEmpty()) {
039                int actions = MOVE;
040                if (onlyDataLayersSelected(tableModel)) {
041                    actions |= COPY;
042                }
043                return actions /* soon: | LINK*/;
044            }
045        }
046        return NONE;
047    }
048
049    private static boolean onlyDataLayersSelected(LayerListModel tableModel) {
050        return tableModel.getSelectedLayers().stream().allMatch(l -> l instanceof OsmDataLayer);
051    }
052
053    @Override
054    protected Transferable createTransferable(JComponent c) {
055        if (c instanceof JTable) {
056            LayerListModel tableModel = (LayerListModel) ((JTable) c).getModel();
057            return new LayerTransferable(tableModel.getLayerManager(), tableModel.getSelectedLayers());
058        }
059        return null;
060    }
061
062    @Override
063    public boolean canImport(TransferSupport support) {
064        if (support.isDrop()) {
065            support.setShowDropLocation(true);
066        }
067
068        if (!support.isDataFlavorSupported(LayerTransferable.LAYER_DATA)) {
069            return false;
070        }
071
072        // cannot link yet.
073        return support.getDropAction() != LINK;
074    }
075
076    @Override
077    public boolean importData(TransferSupport support) {
078        try {
079            LayerListModel tableModel = (LayerListModel) ((JTable) support.getComponent()).getModel();
080
081            LayerTransferable.Data layers = (LayerTransferable.Data) support.getTransferable()
082                    .getTransferData(LayerTransferable.LAYER_DATA);
083
084            int dropLocation;
085            if (support.isDrop()) {
086                DropLocation dl = support.getDropLocation();
087                if (dl instanceof JTable.DropLocation) {
088                    dropLocation = ((JTable.DropLocation) dl).getRow();
089                } else {
090                    dropLocation = 0;
091                }
092            } else {
093                dropLocation = layers.getLayers().get(0).getDefaultLayerPosition().getPosition(layers.getManager());
094            }
095
096            boolean isSameLayerManager = tableModel.getLayerManager() == layers.getManager();
097
098            if (isSameLayerManager && support.getDropAction() == MOVE) {
099                for (Layer layer : layers.getLayers()) {
100                    boolean wasBeforeInsert = layers.getManager().getLayers().indexOf(layer) <= dropLocation;
101                    if (wasBeforeInsert) {
102                        // need to move insertion point one down to preserve order
103                        dropLocation--;
104                    }
105                    layers.getManager().moveLayer(layer, dropLocation);
106                    dropLocation++;
107                }
108            } else {
109                List<Layer> layersToUse = layers.getLayers();
110                if (support.getDropAction() == COPY) {
111                    layersToUse = createCopy(layersToUse, layers.getManager().getLayers());
112                }
113                for (Layer layer : layersToUse) {
114                    layers.getManager().addLayer(layer);
115                    layers.getManager().moveLayer(layer, dropLocation);
116                    dropLocation++;
117                }
118            }
119
120            return true;
121        } catch (UnsupportedFlavorException e) {
122            Logging.warn("Flavor not supported", e);
123            return false;
124        } catch (IOException e) {
125            Logging.warn("Error while pasting layer", e);
126            return false;
127        }
128    }
129
130    private static List<Layer> createCopy(List<Layer> layersToUse, List<Layer> namesToAvoid) {
131        Collection<String> layerNames = getNames(namesToAvoid);
132        ArrayList<Layer> layers = new ArrayList<>();
133        for (Layer layer : layersToUse) {
134            if (layer instanceof OsmDataLayer) {
135                String newName = suggestNewLayerName(layer.getName(), layerNames);
136                OsmDataLayer newLayer = new OsmDataLayer(new DataSet(((OsmDataLayer) layer).getDataSet()), newName, null);
137                layers.add(newLayer);
138                layerNames.add(newName);
139            }
140        }
141        return layers;
142    }
143
144    /**
145     * Suggests a new name in the form "copy of name"
146     * @param name The base name
147     * @param namesToAvoid The list of layers to use to avoid duplicate names.
148     * @return The new name
149     */
150    public static String suggestNewLayerName(String name, List<Layer> namesToAvoid) {
151        Collection<String> layerNames = getNames(namesToAvoid);
152
153        return suggestNewLayerName(name, layerNames);
154    }
155
156    private static List<String> getNames(List<Layer> namesToAvoid) {
157        return namesToAvoid.stream().map(Layer::getName).collect(Collectors.toList());
158    }
159
160    private static String suggestNewLayerName(String name, Collection<String> layerNames) {
161        // Translators: "Copy of {layer name}"
162        String newName = tr("Copy of {0}", name);
163        int i = 2;
164        while (layerNames.contains(newName)) {
165            // Translators: "Copy {number} of {layer name}"
166            newName = tr("Copy {1} of {0}", name, i);
167            i++;
168        }
169        return newName;
170    }
171}