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.data.osm.DataSet;
018import org.openstreetmap.josm.gui.datatransfer.LayerTransferable;
019import org.openstreetmap.josm.gui.dialogs.LayerListDialog.LayerListModel;
020import org.openstreetmap.josm.gui.layer.Layer;
021import org.openstreetmap.josm.gui.layer.OsmDataLayer;
022import org.openstreetmap.josm.tools.Logging;
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        if (c instanceof JTable) {
036            LayerListModel tableModel = (LayerListModel) ((JTable) c).getModel();
037            if (!tableModel.getSelectedLayers().isEmpty()) {
038                int actions = MOVE;
039                if (onlyDataLayersSelected(tableModel)) {
040                    actions |= COPY;
041                }
042                return actions /* soon: | LINK*/;
043            }
044        }
045        return NONE;
046    }
047
048    private static boolean onlyDataLayersSelected(LayerListModel tableModel) {
049        for (Layer l : tableModel.getSelectedLayers()) {
050            if (!(l instanceof OsmDataLayer)) {
051                return false;
052            }
053        }
054        return true;
055    }
056
057    @Override
058    protected Transferable createTransferable(JComponent c) {
059        if (c instanceof JTable) {
060            LayerListModel tableModel = (LayerListModel) ((JTable) c).getModel();
061            return new LayerTransferable(tableModel.getLayerManager(), tableModel.getSelectedLayers());
062        }
063        return null;
064    }
065
066    @Override
067    public boolean canImport(TransferSupport support) {
068        if (support.isDrop()) {
069            support.setShowDropLocation(true);
070        }
071
072        if (!support.isDataFlavorSupported(LayerTransferable.LAYER_DATA)) {
073            return false;
074        }
075
076        // cannot link yet.
077        return support.getDropAction() != LINK;
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                DropLocation dl = support.getDropLocation();
091                if (dl instanceof JTable.DropLocation) {
092                    dropLocation = ((JTable.DropLocation) dl).getRow();
093                } else {
094                    dropLocation = 0;
095                }
096            } else {
097                dropLocation = layers.getLayers().get(0).getDefaultLayerPosition().getPosition(layers.getManager());
098            }
099
100            boolean isSameLayerManager = tableModel.getLayerManager() == layers.getManager();
101
102            if (isSameLayerManager && support.getDropAction() == MOVE) {
103                for (Layer layer : layers.getLayers()) {
104                    boolean wasBeforeInsert = layers.getManager().getLayers().indexOf(layer) <= dropLocation;
105                    if (wasBeforeInsert) {
106                        // need to move insertion point one down to preserve order
107                        dropLocation--;
108                    }
109                    layers.getManager().moveLayer(layer, dropLocation);
110                    dropLocation++;
111                }
112            } else {
113                List<Layer> layersToUse = layers.getLayers();
114                if (support.getDropAction() == COPY) {
115                    layersToUse = createCopy(layersToUse, layers.getManager().getLayers());
116                }
117                for (Layer layer : layersToUse) {
118                    layers.getManager().addLayer(layer);
119                    layers.getManager().moveLayer(layer, dropLocation);
120                    dropLocation++;
121                }
122            }
123
124            return true;
125        } catch (UnsupportedFlavorException e) {
126            Logging.warn("Flavor not supported", e);
127            return false;
128        } catch (IOException e) {
129            Logging.warn("Error while pasting layer", e);
130            return false;
131        }
132    }
133
134    private static List<Layer> createCopy(List<Layer> layersToUse, List<Layer> namesToAvoid) {
135        Collection<String> layerNames = getNames(namesToAvoid);
136        ArrayList<Layer> layers = new ArrayList<>();
137        for (Layer layer : layersToUse) {
138            if (layer instanceof OsmDataLayer) {
139                String newName = suggestNewLayerName(layer.getName(), layerNames);
140                OsmDataLayer newLayer = new OsmDataLayer(new DataSet(((OsmDataLayer) layer).data), newName, null);
141                layers.add(newLayer);
142                layerNames.add(newName);
143            }
144        }
145        return layers;
146    }
147
148    /**
149     * Suggests a new name in the form "copy of name"
150     * @param name The base name
151     * @param namesToAvoid The list of layers to use to avoid duplicate names.
152     * @return The new name
153     */
154    public static String suggestNewLayerName(String name, List<Layer> namesToAvoid) {
155        Collection<String> layerNames = getNames(namesToAvoid);
156
157        return suggestNewLayerName(name, layerNames);
158    }
159
160    private static List<String> getNames(List<Layer> namesToAvoid) {
161        List<String> layerNames = new ArrayList<>();
162        for (Layer l: namesToAvoid) {
163            layerNames.add(l.getName());
164        }
165        return layerNames;
166    }
167
168    private static String suggestNewLayerName(String name, Collection<String> layerNames) {
169        // Translators: "Copy of {layer name}"
170        String newName = tr("Copy of {0}", name);
171        int i = 2;
172        while (layerNames.contains(newName)) {
173            // Translators: "Copy {number} of {layer name}"
174            newName = tr("Copy {1} of {0}", name, i);
175            i++;
176        }
177        return newName;
178    }
179}