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