Index: trunk/src/org/openstreetmap/josm/actions/AbstractMergeAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/AbstractMergeAction.java	(revision 14337)
+++ trunk/src/org/openstreetmap/josm/actions/AbstractMergeAction.java	(revision 14338)
@@ -9,4 +9,5 @@
 
 import javax.swing.DefaultListCellRenderer;
+import javax.swing.JCheckBox;
 import javax.swing.JLabel;
 import javax.swing.JList;
@@ -40,4 +41,45 @@
             label.setToolTipText(layer.getToolTipText());
             return label;
+        }
+    }
+
+    /**
+     * <code>TargetLayerDialogResult</code> returned by {@link #askTargetLayer(List, String, boolean)}
+     * containing the selectedTargetLayer and whether the checkbox is ticked
+     * @param <T> type of layer
+     * @since 14338
+     */
+    public static class TargetLayerDialogResult<T extends Layer> {
+        /**
+         * The selected target layer of type T
+         */
+        public T selectedTargetLayer;
+        /**
+         * Whether the checkbox is ticked
+         */
+        public boolean checkboxTicked = false;
+
+        /**
+         * Constructs a new {@link TargetLayerDialogResult}
+         */
+        public TargetLayerDialogResult() {
+        }
+
+        /**
+         * Constructs a new {@link TargetLayerDialogResult}
+         * @param sel the selected target layer of type T
+         */
+        public TargetLayerDialogResult(T sel) {
+            selectedTargetLayer = sel;
+        }
+
+        /**
+         * Constructs a new {@link TargetLayerDialogResult}
+         * @param sel the selected target layer of type T
+         * @param ch whether the checkbox was ticked
+         */
+        public TargetLayerDialogResult(T sel, boolean ch) {
+            selectedTargetLayer = sel;
+            checkboxTicked = ch;
         }
     }
@@ -84,12 +126,36 @@
      */
     protected static Layer askTargetLayer(List<Layer> targetLayers) {
+        return askTargetLayer(targetLayers, false, null, false).selectedTargetLayer;
+    }
+
+    /**
+     * Ask user to choose the target layer and shows a checkbox.
+     * @param targetLayers list of candidate target layers.
+     * @param checkbox The text of the checkbox shown to the user.
+     * @param checkboxDefault whether the checkbox is ticked by default
+     * @return The {@link TargetLayerDialogResult} containing the chosen target layer and the state of the checkbox
+     */
+    protected static TargetLayerDialogResult<Layer> askTargetLayer(List<Layer> targetLayers, String checkbox, boolean checkboxDefault) {
+        return askTargetLayer(targetLayers, true, checkbox, checkboxDefault);
+    }
+
+    /**
+     * Ask user to choose the target layer and shows a checkbox.
+     * @param targetLayers list of candidate target layers.
+     * @param showCheckbox whether the checkbox is shown
+     * @param checkbox The text of the checkbox shown to the user.
+     * @param checkboxDefault whether the checkbox is ticked by default
+     * @return The {@link TargetLayerDialogResult} containing the chosen target layer and the state of the checkbox
+     */
+    protected static TargetLayerDialogResult<Layer> askTargetLayer(List<Layer> targetLayers, boolean showCheckbox,
+            String checkbox, boolean checkboxDefault) {
         return askTargetLayer(targetLayers.toArray(new Layer[0]),
-                tr("Please select the target layer."),
+                tr("Please select the target layer."), checkbox,
                 tr("Select target layer"),
-                tr("Merge"), "dialogs/mergedown");
-    }
-
-    /**
-     * Asks a target layer.
+                tr("Merge"), "dialogs/mergedown", showCheckbox, checkboxDefault);
+    }
+
+    /**
+     * Ask user to choose the target layer.
      * @param <T> type of layer
      * @param targetLayers array of proposed target layers
@@ -100,6 +166,25 @@
      * @return chosen target layer
      */
+    public static <T extends Layer> T askTargetLayer(T[] targetLayers, String label, String title, String buttonText, String buttonIcon) {
+        return askTargetLayer(targetLayers, label, null, title, buttonText, buttonIcon, false, false).selectedTargetLayer;
+    }
+
+    /**
+     * Ask user to choose the target layer. Can show a checkbox.
+     * @param <T> type of layer
+     * @param targetLayers array of proposed target layers
+     * @param label label displayed in dialog
+     * @param checkbox text of the checkbox displayed
+     * @param title title of dialog
+     * @param buttonText text of button used to select target layer
+     * @param buttonIcon icon name of button used to select target layer
+     * @param showCheckbox whether the checkbox is shown
+     * @param checkboxDefault whether the checkbox is ticked by default
+     * @return The {@link TargetLayerDialogResult} containing the chosen target layer and the state of the checkbox
+     * @since 14338
+     */
     @SuppressWarnings("unchecked")
-    public static <T extends Layer> T askTargetLayer(T[] targetLayers, String label, String title, String buttonText, String buttonIcon) {
+    public static <T extends Layer> TargetLayerDialogResult<T> askTargetLayer(T[] targetLayers, String label, String checkbox, String title,
+            String buttonText, String buttonIcon, boolean showCheckbox, boolean checkboxDefault) {
         JosmComboBox<T> layerList = new JosmComboBox<>(targetLayers);
         layerList.setRenderer(new LayerListCellRenderer());
@@ -109,4 +194,11 @@
         pnl.add(new JLabel(label), GBC.eol());
         pnl.add(layerList, GBC.eol().fill(GBC.HORIZONTAL));
+
+        JCheckBox cb = null;
+        if (showCheckbox) {
+            cb = new JCheckBox(checkbox);
+            cb.setSelected(checkboxDefault);
+            pnl.add(cb, GBC.eol());
+        }
 
         ExtendedDialog ed = new ExtendedDialog(MainApplication.getMainFrame(), title, buttonText, tr("Cancel"));
@@ -115,7 +207,7 @@
         ed.showDialog();
         if (ed.getValue() != 1) {
-            return null;
-        }
-        return (T) layerList.getSelectedItem();
+            return new TargetLayerDialogResult<>();
+        }
+        return new TargetLayerDialogResult<>((T) layerList.getSelectedItem(), cb != null && cb.isSelected());
     }
 
Index: trunk/src/org/openstreetmap/josm/actions/MergeLayerAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/MergeLayerAction.java	(revision 14337)
+++ trunk/src/org/openstreetmap/josm/actions/MergeLayerAction.java	(revision 14338)
@@ -7,4 +7,5 @@
 import java.awt.event.ActionEvent;
 import java.awt.event.KeyEvent;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -14,7 +15,10 @@
 import org.openstreetmap.josm.gui.MainApplication;
 import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
+import org.openstreetmap.josm.gui.dialogs.layer.MergeGpxLayerDialog;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
 import org.openstreetmap.josm.gui.layer.Layer;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.openstreetmap.josm.spi.preferences.Config;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.Logging;
@@ -48,28 +52,67 @@
      */
     protected Future<?> doMerge(List<Layer> targetLayers, final Collection<Layer> sourceLayers) {
-        final Layer targetLayer = askTargetLayer(targetLayers);
+        final boolean onlygpx = targetLayers.stream().noneMatch(l -> !(l instanceof GpxLayer));
+        final TargetLayerDialogResult<Layer> res = askTargetLayer(targetLayers, onlygpx,
+                tr("Cut timewise overlapping parts of tracks"),
+                onlygpx && Config.getPref().getBoolean("mergelayer.gpx.cut", false));
+        final Layer targetLayer = res.selectedTargetLayer;
         if (targetLayer == null)
             return null;
+
+        if (onlygpx) {
+            Config.getPref().putBoolean("mergelayer.gpx.cut", res.checkboxTicked);
+        }
+
         final Object actionName = getValue(NAME);
+        if (onlygpx && res.checkboxTicked) {
+            List<GpxLayer> layers = new ArrayList<>();
+            layers.add((GpxLayer) targetLayer);
+            for (Layer sl : sourceLayers) {
+                if (sl != null && !sl.equals(targetLayer)) {
+                    layers.add((GpxLayer) sl);
+                }
+            }
+            final MergeGpxLayerDialog d = new MergeGpxLayerDialog(MainApplication.getMainFrame(), layers);
+
+            if (d.showDialog().getValue() == 1) {
+
+                final boolean connect = d.connectCuts();
+                final List<GpxLayer> sortedLayers = d.getSortedLayers();
+
+                return MainApplication.worker.submit(() -> {
+                    final long start = System.currentTimeMillis();
+
+                    for (int i = sortedLayers.size() - 2; i >= 0; i--) {
+                        final GpxLayer lower = sortedLayers.get(i + 1);
+                        sortedLayers.get(i).mergeFrom(lower, true, connect);
+                        GuiHelper.runInEDTAndWait(() -> getLayerManager().removeLayer(lower));
+                    }
+
+                    Logging.info(tr("{0} completed in {1}", actionName, Utils.getDurationString(System.currentTimeMillis() - start)));
+                });
+            }
+        }
+
         return MainApplication.worker.submit(() -> {
-                final long start = System.currentTimeMillis();
-                boolean layerMerged = false;
-                for (final Layer sourceLayer: sourceLayers) {
-                    if (sourceLayer != null && !sourceLayer.equals(targetLayer)) {
-                        if (sourceLayer instanceof OsmDataLayer && targetLayer instanceof OsmDataLayer
-                                && ((OsmDataLayer) sourceLayer).isUploadDiscouraged() != ((OsmDataLayer) targetLayer).isUploadDiscouraged()
-                                && Boolean.TRUE.equals(GuiHelper.runInEDTAndWaitAndReturn(() ->
-                                    warnMergingUploadDiscouragedLayers(sourceLayer, targetLayer)))) {
-                            break;
-                        }
-                        targetLayer.mergeFrom(sourceLayer);
-                        GuiHelper.runInEDTAndWait(() -> getLayerManager().removeLayer(sourceLayer));
-                        layerMerged = true;
+            final long start = System.currentTimeMillis();
+            boolean layerMerged = false;
+            for (final Layer sourceLayer: sourceLayers) {
+                if (sourceLayer != null && !sourceLayer.equals(targetLayer)) {
+                    if (sourceLayer instanceof OsmDataLayer && targetLayer instanceof OsmDataLayer
+                            && ((OsmDataLayer) sourceLayer).isUploadDiscouraged() != ((OsmDataLayer) targetLayer).isUploadDiscouraged()
+                            && Boolean.TRUE.equals(GuiHelper.runInEDTAndWaitAndReturn(() ->
+                            warnMergingUploadDiscouragedLayers(sourceLayer, targetLayer)))) {
+                        break;
                     }
+                    targetLayer.mergeFrom(sourceLayer);
+                    GuiHelper.runInEDTAndWait(() -> getLayerManager().removeLayer(sourceLayer));
+                    layerMerged = true;
                 }
-                if (layerMerged) {
-                    getLayerManager().setActiveLayer(targetLayer);
-                    Logging.info(tr("{0} completed in {1}", actionName, Utils.getDurationString(System.currentTimeMillis() - start)));
-                }
+            }
+
+            if (layerMerged) {
+                getLayerManager().setActiveLayer(targetLayer);
+                Logging.info(tr("{0} completed in {1}", actionName, Utils.getDurationString(System.currentTimeMillis() - start)));
+            }
         });
     }
Index: trunk/src/org/openstreetmap/josm/data/gpx/GpxData.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/gpx/GpxData.java	(revision 14337)
+++ trunk/src/org/openstreetmap/josm/data/gpx/GpxData.java	(revision 14338)
@@ -59,5 +59,5 @@
     private final ArrayList<GpxTrack> privateTracks = new ArrayList<>();
     /**
-     * GXP routes in this file
+     * GPX routes in this file
      */
     private final ArrayList<GpxRoute> privateRoutes = new ArrayList<>();
@@ -109,4 +109,8 @@
     private final ListenerList<GpxDataChangeListener> listeners = ListenerList.create();
 
+    static class TimestampConfictException extends Exception {}
+
+    private List<GpxTrackSegmentSpan> segSpans;
+
     /**
      * Merges data from another object.
@@ -114,4 +118,15 @@
      */
     public synchronized void mergeFrom(GpxData other) {
+        mergeFrom(other, false, false);
+    }
+
+    /**
+     * Merges data from another object.
+     * @param other existing GPX data
+     * @param cutOverlapping whether overlapping parts of the given track should be removed
+     * @param connect whether the tracks should be connected on cuts
+     * @since 14338
+     */
+    public synchronized void mergeFrom(GpxData other, boolean cutOverlapping, boolean connect) {
         if (storageFile == null && other.storageFile != null) {
             storageFile = other.storageFile;
@@ -131,9 +146,220 @@
             }
         }
-        other.privateTracks.forEach(this::addTrack);
+
+        if (cutOverlapping) {
+            for (GpxTrack trk : other.privateTracks) {
+                cutOverlapping(trk, connect);
+            }
+        } else {
+            other.privateTracks.forEach(this::addTrack);
+        }
         other.privateRoutes.forEach(this::addRoute);
         other.privateWaypoints.forEach(this::addWaypoint);
         dataSources.addAll(other.dataSources);
         fireInvalidate();
+    }
+
+    private void cutOverlapping(GpxTrack trk, boolean connect) {
+        List<GpxTrackSegment> segsOld = new ArrayList<>(trk.getSegments());
+        List<GpxTrackSegment> segsNew = new ArrayList<>();
+        for (GpxTrackSegment seg : segsOld) {
+            GpxTrackSegmentSpan s = GpxTrackSegmentSpan.tryGetFromSegment(seg);
+            if (s != null && anySegmentOverlapsWith(s)) {
+                List<WayPoint> wpsNew = new ArrayList<>();
+                List<WayPoint> wpsOld = new ArrayList<>(seg.getWayPoints());
+                if (s.isInverted()) {
+                    Collections.reverse(wpsOld);
+                }
+                boolean split = false;
+                WayPoint prevLastOwnWp = null;
+                Date prevWpTime = null;
+                for (WayPoint wp : wpsOld) {
+                    Date wpTime = wp.setTimeFromAttribute();
+                    boolean overlap = false;
+                    if (wpTime != null) {
+                        for (GpxTrackSegmentSpan ownspan : getSegmentSpans()) {
+                            if (wpTime.after(ownspan.firstTime) && wpTime.before(ownspan.lastTime)) {
+                                overlap = true;
+                                if (connect) {
+                                    if (!split) {
+                                        wpsNew.add(ownspan.getFirstWp());
+                                    } else {
+                                        connectTracks(prevLastOwnWp, ownspan, trk.getAttributes());
+                                    }
+                                    prevLastOwnWp = ownspan.getLastWp();
+                                }
+                                split = true;
+                                break;
+                            } else if (connect && prevWpTime != null
+                                    && prevWpTime.before(ownspan.firstTime)
+                                    && wpTime.after(ownspan.lastTime)) {
+                                // the overlapping high priority track is shorter than the distance
+                                // between two waypoints of the low priority track
+                                if (split) {
+                                    connectTracks(prevLastOwnWp, ownspan, trk.getAttributes());
+                                    prevLastOwnWp = ownspan.getLastWp();
+                                } else {
+                                    wpsNew.add(ownspan.getFirstWp());
+                                    // splitting needs to be handled here,
+                                    // because other high priority tracks between the same waypoints could follow
+                                    if (!wpsNew.isEmpty()) {
+                                        segsNew.add(new ImmutableGpxTrackSegment(wpsNew));
+                                    }
+                                    if (!segsNew.isEmpty()) {
+                                        privateTracks.add(new ImmutableGpxTrack(segsNew, trk.getAttributes()));
+                                    }
+                                    segsNew = new ArrayList<>();
+                                    wpsNew = new ArrayList<>();
+                                    wpsNew.add(ownspan.getLastWp());
+                                    // therefore no break, because another segment could overlap, see above
+                                }
+                            }
+                        }
+                        prevWpTime = wpTime;
+                    }
+                    if (!overlap) {
+                        if (split) {
+                            //track has to be split, because we have an overlapping short track in the middle
+                            if (!wpsNew.isEmpty()) {
+                                segsNew.add(new ImmutableGpxTrackSegment(wpsNew));
+                            }
+                            if (!segsNew.isEmpty()) {
+                                privateTracks.add(new ImmutableGpxTrack(segsNew, trk.getAttributes()));
+                            }
+                            segsNew = new ArrayList<>();
+                            wpsNew = new ArrayList<>();
+                            if (connect && prevLastOwnWp != null) {
+                                wpsNew.add(new WayPoint(prevLastOwnWp));
+                            }
+                            prevLastOwnWp = null;
+                            split = false;
+                        }
+                        wpsNew.add(new WayPoint(wp));
+                    }
+                }
+                if (!wpsNew.isEmpty()) {
+                    segsNew.add(new ImmutableGpxTrackSegment(wpsNew));
+                }
+            } else {
+                segsNew.add(seg);
+            }
+        }
+        if (segsNew.equals(segsOld)) {
+            privateTracks.add(trk);
+        } else if (!segsNew.isEmpty()) {
+            privateTracks.add(new ImmutableGpxTrack(segsNew, trk.getAttributes()));
+        }
+    }
+
+    private void connectTracks(WayPoint prevWp, GpxTrackSegmentSpan span, Map<String, Object> attr) {
+        if (prevWp != null && !span.lastEquals(prevWp)) {
+            privateTracks.add(new ImmutableGpxTrack(Arrays.asList(Arrays.asList(new WayPoint(prevWp), span.getFirstWp())), attr));
+        }
+    }
+
+    static class GpxTrackSegmentSpan {
+
+        public final Date firstTime;
+        public final Date lastTime;
+        private final boolean inv;
+        private final WayPoint firstWp;
+        private final WayPoint lastWp;
+
+        GpxTrackSegmentSpan(WayPoint a, WayPoint b) {
+            Date at = a.getTime();
+            Date bt = b.getTime();
+            inv = bt.before(at);
+            if (inv) {
+                firstWp = b;
+                firstTime = bt;
+                lastWp = a;
+                lastTime = at;
+            } else {
+                firstWp = a;
+                firstTime = at;
+                lastWp = b;
+                lastTime = bt;
+            }
+        }
+
+        public WayPoint getFirstWp() {
+            return new WayPoint(firstWp);
+        }
+
+        public WayPoint getLastWp() {
+            return new WayPoint(lastWp);
+        }
+
+        // no new instances needed, therefore own methods for that
+
+        public boolean firstEquals(Object other) {
+            return firstWp.equals(other);
+        }
+
+        public boolean lastEquals(Object other) {
+            return lastWp.equals(other);
+        }
+
+        public boolean isInverted() {
+            return inv;
+        }
+
+        public boolean overlapsWith(GpxTrackSegmentSpan other) {
+            return (firstTime.before(other.lastTime) && other.firstTime.before(lastTime))
+                || (other.firstTime.before(lastTime) && firstTime.before(other.lastTime));
+        }
+
+        public static GpxTrackSegmentSpan tryGetFromSegment(GpxTrackSegment seg) {
+            WayPoint b = getNextWpWithTime(seg, true);
+            if (b != null) {
+                WayPoint e = getNextWpWithTime(seg, false);
+                if (e != null) {
+                    return new GpxTrackSegmentSpan(b, e);
+                }
+            }
+            return null;
+        }
+
+        private static WayPoint getNextWpWithTime(GpxTrackSegment seg, boolean forward) {
+            List<WayPoint> wps = new ArrayList<>(seg.getWayPoints());
+            for (int i = forward ? 0 : wps.size() - 1; i >= 0 && i < wps.size(); i += forward ? 1 : -1) {
+                if (wps.get(i).setTimeFromAttribute() != null) {
+                    return wps.get(i);
+                }
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Get a list of SegmentSpans containing the beginning and end of each segment
+     * @return the list of SegmentSpans
+     * @since 14338
+     */
+    public synchronized List<GpxTrackSegmentSpan> getSegmentSpans() {
+        if (segSpans == null) {
+            segSpans = new ArrayList<>();
+            for (GpxTrack trk : privateTracks) {
+                for (GpxTrackSegment seg : trk.getSegments()) {
+                    GpxTrackSegmentSpan s = GpxTrackSegmentSpan.tryGetFromSegment(seg);
+                    if (s != null) {
+                        segSpans.add(s);
+                    }
+                }
+            }
+            segSpans.sort((o1, o2) -> {
+                return o1.firstTime.compareTo(o2.firstTime);
+            });
+        }
+        return segSpans;
+    }
+
+    private boolean anySegmentOverlapsWith(GpxTrackSegmentSpan other) {
+        for (GpxTrackSegmentSpan s : getSegmentSpans()) {
+            if (s.overlapsWith(other)) {
+                return true;
+            }
+        }
+        return false;
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/layer/MergeGpxLayerDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/layer/MergeGpxLayerDialog.java	(revision 14338)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/layer/MergeGpxLayerDialog.java	(revision 14338)
@@ -0,0 +1,216 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.layer;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.TableColumnModel;
+
+import org.openstreetmap.josm.data.SystemOfMeasurement;
+import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
+import org.openstreetmap.josm.spi.preferences.Config;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
+
+/**
+ * The Dialog asking the user to prioritize GPX layers when cutting overlapping tracks.
+ * Shows a checkbox asking whether to combine the tracks on cuts.
+ * @since 14338
+ */
+public class MergeGpxLayerDialog extends ExtendedDialog {
+
+    private final GpxLayersTableModel model;
+    private final JTable t;
+    private final JCheckBox c;
+    private final Component parent;
+    private final JButton btnUp;
+    private final JButton btnDown;
+
+    /**
+     * Constructs a new {@code MergeGpxLayerDialog}
+     * @param parent the parent
+     * @param layers the GpxLayers to choose from
+     */
+    public MergeGpxLayerDialog(Component parent, List<GpxLayer> layers) {
+        super(parent, tr("Merge GPX layers"), tr("Merge"), tr("Cancel"));
+        setButtonIcons("dialogs/mergedown", "cancel");
+        this.parent = parent;
+
+        JPanel p = new JPanel(new GridBagLayout());
+        p.add(new JLabel("<html>" +
+                tr("Please select the order of the selected layers:<br>Tracks will be cut, when timestamps of higher layers are overlapping.") +
+                "</html>"), GBC.std(0, 0).fill(GBC.HORIZONTAL).span(2));
+
+        c = new JCheckBox(tr("Connect overlapping tracks on cuts"));
+        c.setSelected(Config.getPref().getBoolean("mergelayer.gpx.connect", true));
+        p.add(c, GBC.std(0, 1).fill(GBC.HORIZONTAL).span(2));
+
+        model = new GpxLayersTableModel(layers);
+        t = new JTable(model);
+        t.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        t.setRowSelectionInterval(0, 0);
+
+        JScrollPane sp = new JScrollPane(t);
+        p.add(sp, GBC.std(0, 2).fill().span(2));
+
+        t.getSelectionModel().addListSelectionListener(new RowSelectionChangedListener());
+        TableColumnModel cmodel = t.getColumnModel();
+        cmodel.getColumn(0).setPreferredWidth((int) (sp.getPreferredSize().getWidth() - 150));
+        cmodel.getColumn(1).setPreferredWidth(75);
+        cmodel.getColumn(2).setPreferredWidth(75);
+
+        btnUp = new JButton(tr("Move layer up"));
+        btnUp.setIcon(ImageProvider.get("dialogs", "up", ImageSizes.SMALLICON));
+        btnUp.setEnabled(false);
+
+        btnDown = new JButton(tr("Move layer down"));
+        btnDown.setIcon(ImageProvider.get("dialogs", "down", ImageSizes.SMALLICON));
+
+        p.add(btnUp, GBC.std(0, 3).fill(GBC.HORIZONTAL));
+        p.add(btnDown, GBC.std(1, 3).fill(GBC.HORIZONTAL));
+
+        btnUp.addActionListener(new MoveLayersActionListener(true));
+        btnDown.addActionListener(new MoveLayersActionListener(false));
+
+        setContent(p);
+    }
+
+    @Override
+    public MergeGpxLayerDialog showDialog() {
+        super.showDialog();
+        if (getValue() == 1) {
+            Config.getPref().putBoolean("mergelayer.gpx.connect", c.isSelected());
+        }
+        return this;
+    }
+
+    /**
+     * Whether the user chose to connect the tracks on cuts
+     * @return the checkbox state
+     */
+    public boolean connectCuts() {
+        return c.isSelected();
+    }
+
+    /**
+     * The {@code List<GpxLayer>} as sorted by the user
+     * @return the list
+     */
+    public List<GpxLayer> getSortedLayers() {
+        return model.getSortedLayers();
+    }
+
+    private class MoveLayersActionListener implements ActionListener {
+
+        private final boolean moveUp;
+
+        MoveLayersActionListener(boolean up) {
+            moveUp = up;
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            int row = t.getSelectedRow();
+            int newRow = row + (moveUp ? -1 : 1);
+
+            if ((row == 0 || newRow == 0)
+                    && (!ConditionalOptionPaneUtil.showConfirmationDialog(
+                            "gpx_target_change",
+                            parent,
+                            new JLabel("<html>" +
+                                    tr("This will change the target layer to \"{0}\".<br>Would you like to continue?",
+                                    model.getValueAt(1, 0).toString()) + "</html>"),
+                            tr("Information"),
+                            JOptionPane.OK_CANCEL_OPTION,
+                            JOptionPane.INFORMATION_MESSAGE,
+                            JOptionPane.OK_OPTION))) {
+                return;
+            }
+
+            model.moveRow(row, newRow);
+            t.getSelectionModel().setSelectionInterval(newRow, newRow);
+            t.repaint();
+        }
+    }
+
+    private class RowSelectionChangedListener implements ListSelectionListener {
+
+        @Override
+        public void valueChanged(ListSelectionEvent e) {
+            btnUp.setEnabled(t.getSelectedRow() > 0);
+            btnDown.setEnabled(t.getSelectedRow() < model.getRowCount() - 1);
+        }
+    }
+
+    private static class GpxLayersTableModel extends AbstractTableModel {
+
+        private final String[] cols = {tr("GPX layer"), tr("Length"), tr("Segments")};
+        private final List<GpxLayer> layers;
+
+        GpxLayersTableModel(List<GpxLayer> l) {
+            layers = l;
+        }
+
+        @Override
+        public String getColumnName(int column) {
+            return cols[column];
+        }
+
+        @Override
+        public int getColumnCount() {
+            return cols.length;
+        }
+
+        @Override
+        public int getRowCount() {
+            return layers.size();
+
+        }
+
+        public void moveRow(int row, int newRow) {
+            Collections.swap(layers, row, newRow);
+        }
+
+        public List<GpxLayer> getSortedLayers() {
+            return layers;
+        }
+
+        @Override
+        public Object getValueAt(int row, int col) {
+            switch (col) {
+            case 0:
+                String n = layers.get(row).getName();
+                if (row == 0) {
+                    return tr("{0} (target layer)", n);
+                } else {
+                    return n;
+                }
+            case 1:
+                return SystemOfMeasurement.getSystemOfMeasurement().getDistText(layers.get(row).data.length());
+            case 2:
+                return layers.get(row).data.getTrackSegsCount();
+            }
+            throw new IndexOutOfBoundsException(Integer.toString(col));
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java	(revision 14337)
+++ trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java	(revision 14338)
@@ -307,5 +307,16 @@
         if (!(from instanceof GpxLayer))
             throw new IllegalArgumentException("not a GpxLayer: " + from);
-        data.mergeFrom(((GpxLayer) from).data);
+        mergeFrom((GpxLayer) from, false, false);
+    }
+
+    /**
+     * Merges the given GpxLayer into this layer and can remove timewise overlapping parts of the given track
+     * @param from The GpxLayer that gets merged into this one
+     * @param cutOverlapping whether overlapping parts of the given track should be removed
+     * @param connect whether the tracks should be connected on cuts
+     * @since 14338
+     */
+    public void mergeFrom(GpxLayer from, boolean cutOverlapping, boolean connect) {
+        data.mergeFrom(from.data, cutOverlapping, connect);
         invalidate();
     }
