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;
012
013import javax.swing.JComponent;
014import javax.swing.JTable;
015import javax.swing.TransferHandler;
016
017import org.openstreetmap.josm.Main;
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;
023
024/**
025 * This class allows the user to transfer layers using drag+drop.
026 * <p>
027 * It supports copy (duplication) of layers, simple moves and linking layers to a new layer manager.
028 *
029 * @author Michael Zangl
030 * @since 10605
031 */
032public class LayerListTransferHandler extends TransferHandler {
033    @Override
034    public int getSourceActions(JComponent c) {
035        // we know that the source is a layer list, so don't check c.
036        LayerListModel tableModel = (LayerListModel) ((JTable) c).getModel();
037        if (tableModel.getSelectedLayers().isEmpty()) {
038            return 0;
039        }
040        int actions = MOVE;
041        if (onlyDataLayersSelected(tableModel)) {
042            actions |= COPY;
043        }
044        return actions /* soon: | LINK*/;
045    }
046
047    private static boolean onlyDataLayersSelected(LayerListModel tableModel) {
048        for (Layer l : tableModel.getSelectedLayers()) {
049            if (!(l instanceof OsmDataLayer)) {
050                return false;
051            }
052        }
053        return true;
054    }
055
056    @Override
057    protected Transferable createTransferable(JComponent c) {
058        LayerListModel tableModel = (LayerListModel) ((JTable) c).getModel();
059        return new LayerTransferable(tableModel.getLayerManager(), tableModel.getSelectedLayers());
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        if (support.getDropAction() == LINK) {
073            // cannot link yet.
074            return false;
075        }
076
077        return true;
078    }
079
080    @Override
081    public boolean importData(TransferSupport support) {
082        try {
083            LayerListModel tableModel = (LayerListModel) ((JTable) support.getComponent()).getModel();
084
085            LayerTransferable.Data layers = (LayerTransferable.Data) support.getTransferable()
086                    .getTransferData(LayerTransferable.LAYER_DATA);
087
088            int dropLocation;
089            if (support.isDrop()) {
090                JTable.DropLocation dl = (JTable.DropLocation) support.getDropLocation();
091                dropLocation = dl.getRow();
092            } else {
093                dropLocation = layers.getLayers().get(0).getDefaultLayerPosition().getPosition(layers.getManager());
094            }
095
096            boolean isSameLayerManager = tableModel.getLayerManager() == layers.getManager();
097
098            if (support.getDropAction() == MOVE && isSameLayerManager) {
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            Main.warn("Flavor not supported", e);
123            return false;
124        } catch (IOException e) {
125            Main.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).data), 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        List<String> layerNames = new ArrayList<>();
158        for (Layer l: namesToAvoid) {
159            layerNames.add(l.getName());
160        }
161        return layerNames;
162    }
163
164    private static String suggestNewLayerName(String name, Collection<String> layerNames) {
165        // Translators: "Copy of {layer name}"
166        String newName = tr("Copy of {0}", name);
167        int i = 2;
168        while (layerNames.contains(newName)) {
169            // Translators: "Copy {number} of {layer name}"
170            newName = tr("Copy {1} of {0}", name, i);
171            i++;
172        }
173        return newName;
174    }
175}