001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.actions;
003
004import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
005import static org.openstreetmap.josm.tools.I18n.tr;
006
007import java.awt.event.ActionEvent;
008import java.awt.event.KeyEvent;
009import java.util.Collection;
010import java.util.Collections;
011import java.util.List;
012
013import org.openstreetmap.josm.Main;
014import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
015import org.openstreetmap.josm.gui.layer.Layer;
016import org.openstreetmap.josm.gui.layer.OsmDataLayer;
017import org.openstreetmap.josm.gui.util.GuiHelper;
018import org.openstreetmap.josm.tools.ImageProvider;
019import org.openstreetmap.josm.tools.Shortcut;
020import org.openstreetmap.josm.tools.Utils;
021
022/**
023 * Action that merges two or more OSM data layers.
024 * @since 1890
025 */
026public class MergeLayerAction extends AbstractMergeAction {
027
028    /**
029     * Constructs a new {@code MergeLayerAction}.
030     */
031    public MergeLayerAction() {
032        super(tr("Merge layer"), "dialogs/mergedown",
033            tr("Merge the current layer into another layer"),
034            Shortcut.registerShortcut("system:merge", tr("Edit: {0}",
035            tr("Merge")), KeyEvent.VK_M, Shortcut.CTRL),
036            true, "action/mergelayer", true);
037        putValue("help", ht("/Action/MergeLayer"));
038    }
039
040    protected void doMerge(List<Layer> targetLayers, final Collection<Layer> sourceLayers) {
041        final Layer targetLayer = askTargetLayer(targetLayers);
042        if (targetLayer == null)
043            return;
044        final Object actionName = MergeLayerAction.this.getValue(NAME);
045        Main.worker.submit(() -> {
046                final long start = System.currentTimeMillis();
047                boolean layerMerged = false;
048                for (final Layer sourceLayer: sourceLayers) {
049                    if (sourceLayer != null && !sourceLayer.equals(targetLayer)) {
050                        if (sourceLayer instanceof OsmDataLayer && targetLayer instanceof OsmDataLayer
051                                && ((OsmDataLayer) sourceLayer).isUploadDiscouraged() != ((OsmDataLayer) targetLayer).isUploadDiscouraged()
052                                && Boolean.TRUE.equals(GuiHelper.runInEDTAndWaitAndReturn(() ->
053                                    warnMergingUploadDiscouragedLayers(sourceLayer, targetLayer)))) {
054                            break;
055                        }
056                        targetLayer.mergeFrom(sourceLayer);
057                        GuiHelper.runInEDTAndWait(() -> Main.getLayerManager().removeLayer(sourceLayer));
058                        layerMerged = true;
059                    }
060                }
061                if (layerMerged) {
062                    Main.getLayerManager().setActiveLayer(targetLayer);
063                    Main.info(tr("{0} completed in {1}", actionName, Utils.getDurationString(System.currentTimeMillis() - start)));
064                }
065        });
066    }
067
068    /**
069     * Merges a list of layers together.
070     * @param sourceLayers The layers to merge
071     */
072    public void merge(List<Layer> sourceLayers) {
073        doMerge(sourceLayers, sourceLayers);
074    }
075
076    /**
077     * Merges the given source layer with another one, determined at runtime.
078     * @param sourceLayer The source layer to merge
079     */
080    public void merge(Layer sourceLayer) {
081        if (sourceLayer == null)
082            return;
083        List<Layer> targetLayers = LayerListDialog.getInstance().getModel().getPossibleMergeTargets(sourceLayer);
084        if (targetLayers.isEmpty()) {
085            warnNoTargetLayersForSourceLayer(sourceLayer);
086            return;
087        }
088        doMerge(targetLayers, Collections.singleton(sourceLayer));
089    }
090
091    @Override
092    public void actionPerformed(ActionEvent e) {
093        merge(getSourceLayer());
094    }
095
096    @Override
097    protected void updateEnabledState() {
098        GuiHelper.runInEDT(() -> {
099                final Layer sourceLayer = getSourceLayer();
100                if (sourceLayer == null) {
101                    setEnabled(false);
102                } else {
103                    final List<Layer> possibleMergeTargets = LayerListDialog.getInstance().getModel().getPossibleMergeTargets(sourceLayer);
104                    setEnabled(!possibleMergeTargets.isEmpty());
105                }
106        });
107    }
108
109    protected Layer getSourceLayer() {
110        return Main.map != null ? Main.getLayerManager().getActiveLayer() : null;
111    }
112
113    /**
114     * Warns about a discouraged merge operation, ask for confirmation.
115     * @param sourceLayer The source layer
116     * @param targetLayer The target layer
117     * @return {@code true} if the user wants to cancel, {@code false} if they want to continue
118     */
119    public static final boolean warnMergingUploadDiscouragedLayers(Layer sourceLayer, Layer targetLayer) {
120        return GuiHelper.warnUser(tr("Merging layers with different upload policies"),
121                "<html>" +
122                tr("You are about to merge data between layers ''{0}'' and ''{1}''.<br /><br />"+
123                        "These layers have different upload policies and should not been merged as it.<br />"+
124                        "Merging them will result to enforce the stricter policy (upload discouraged) to ''{1}''.<br /><br />"+
125                        "<b>This is not the recommended way of merging such data</b>.<br />"+
126                        "You should instead check and merge each object, one by one, by using ''<i>Merge selection</i>''.<br /><br />"+
127                        "Are you sure you want to continue?", sourceLayer.getName(), targetLayer.getName(), targetLayer.getName())+
128                "</html>",
129                ImageProvider.get("dialogs", "mergedown"), tr("Ignore this hint and merge anyway"));
130    }
131}