Index: src/org/openstreetmap/josm/actions/SessionLoadAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/SessionLoadAction.java	(revision 18392)
+++ src/org/openstreetmap/josm/actions/SessionLoadAction.java	(working copy)
@@ -205,6 +205,7 @@
                     postLoadTasks = reader.getPostLoadTasks();
                     viewport = reader.getViewport();
                     projectionChoice = reader.getProjectionChoice();
+                    SessionSaveAction.setCurrentSession(file, zip, reader.getLayers());
                 } finally {
                     if (tempFile) {
                         Utils.deleteFile(file);
Index: src/org/openstreetmap/josm/actions/SessionSaveAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/SessionSaveAction.java	(nonexistent)
+++ src/org/openstreetmap/josm/actions/SessionSaveAction.java	(working copy)
@@ -0,0 +1,519 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.actions;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.io.File;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.swing.BorderFactory;
+import javax.swing.JCheckBox;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+import javax.swing.SwingConstants;
+import javax.swing.border.EtchedBorder;
+import javax.swing.filechooser.FileFilter;
+
+import org.openstreetmap.josm.data.PreferencesUtils;
+import org.openstreetmap.josm.data.preferences.BooleanProperty;
+import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.MapFrame;
+import org.openstreetmap.josm.gui.MapFrameListener;
+import org.openstreetmap.josm.gui.Notification;
+import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
+import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
+import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
+import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
+import org.openstreetmap.josm.gui.util.WindowGeometry;
+import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
+import org.openstreetmap.josm.io.session.SessionLayerExporter;
+import org.openstreetmap.josm.io.session.SessionWriter;
+import org.openstreetmap.josm.spi.preferences.Config;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.JosmRuntimeException;
+import org.openstreetmap.josm.tools.Logging;
+import org.openstreetmap.josm.tools.MultiMap;
+import org.openstreetmap.josm.tools.Shortcut;
+import org.openstreetmap.josm.tools.UserCancelException;
+import org.openstreetmap.josm.tools.Utils;
+
+/**
+ * Saves a JOSM session
+ * @since xxx
+ */
+public class SessionSaveAction extends DiskAccessAction implements MapFrameListener, LayerChangeListener {
+
+    private transient List<Layer> layers;
+    private transient Map<Layer, SessionLayerExporter> exporters;
+    private transient MultiMap<Layer, Layer> dependencies;
+
+    private static final BooleanProperty SAVE_LOCAL_FILES_PROPERTY = new BooleanProperty("session.savelocal", true);
+    private static final String TOOLTIP_DEFAULT = tr("Save the current session.");
+
+    protected FileFilter joz = new ExtensionFileFilter("joz", "joz", tr("Session file (archive) (*.joz)"));
+    protected FileFilter jos = new ExtensionFileFilter("jos", "jos", tr("Session file (*.jos)"));
+
+    private File removeFileOnSuccess;
+
+    private static String tooltip = TOOLTIP_DEFAULT;
+    protected static File sessionFile;
+    protected static boolean isZipSessionFile;
+    protected static List<WeakReference<Layer>> layersInSessionFile;
+
+    private static final SessionSaveAction instance = new SessionSaveAction();
+
+    /**
+     * Returns the instance
+     * @return the instance
+     */
+    public static final SessionSaveAction getInstance() {
+        return instance;
+    }
+
+    /**
+     * Constructs a new {@code SessionSaveAction}.
+     */
+    public SessionSaveAction() {
+        this(true, false);
+        updateEnabledState();
+    }
+
+    /**
+     * Constructs a new {@code SessionSaveAction}.
+     * @param toolbar Register this action for the toolbar preferences?
+     * @param installAdapters False, if you don't want to install layer changed and selection changed adapters
+     */
+    protected SessionSaveAction(boolean toolbar, boolean installAdapters) {
+        this(tr("Save Session"), "session", TOOLTIP_DEFAULT,
+                Shortcut.registerShortcut("system:savesession", tr("File: {0}", tr("Save Session...")), KeyEvent.VK_S, Shortcut.ALT_CTRL),
+                toolbar, "save-session", installAdapters);
+        setHelpId(ht("/Action/SessionSaveAs"));
+    }
+
+    protected SessionSaveAction(String name, String iconName, String tooltip,
+            Shortcut shortcut, boolean register, String toolbarId, boolean installAdapters) {
+
+        super(name, iconName, tooltip, shortcut, register, toolbarId, installAdapters);
+        MainApplication.addMapFrameListener(this);
+        MainApplication.getLayerManager().addLayerChangeListener(this);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        try {
+            saveSession(false, false);
+        } catch (UserCancelException ignore) {
+            Logging.trace(ignore);
+        }
+    }
+
+    @Override
+    public void destroy() {
+        MainApplication.removeMapFrameListener(this);
+        super.destroy();
+    }
+
+    /**
+     * Attempts to save the session.
+     * @param saveAs true shows the dialog
+     * @param forceSaveAll saves all layers
+     * @return if the session and all layers were successfully saved
+     * @throws UserCancelException when the user has cancelled the save process
+     */
+    public boolean saveSession(boolean saveAs, boolean forceSaveAll) throws UserCancelException {
+        if (!isEnabled()) {
+            return false;
+        }
+
+        removeFileOnSuccess = null;
+
+        SessionSaveAsDialog dlg = new SessionSaveAsDialog();
+        if (saveAs) {
+            dlg.showDialog();
+            if (dlg.getValue() != 1) {
+                throw new UserCancelException();
+            }
+        }
+
+        // TODO: resolve dependencies for layers excluded by the user
+        List<Layer> layersOut = layers.stream()
+                .filter(layer -> exporters.get(layer) != null && exporters.get(layer).shallExport())
+                .collect(Collectors.toList());
+
+        boolean zipRequired = layersOut.stream().map(l -> exporters.get(l))
+                .anyMatch(ex -> ex != null && ex.requiresZip());
+
+        saveAs = !doGetFile(saveAs, zipRequired);
+
+        String fn = sessionFile.getName();
+
+        if (!saveAs && layersInSessionFile != null) {
+            List<String> missingLayers = layersInSessionFile.stream()
+                    .map(WeakReference::get)
+                    .filter(Objects::nonNull)
+                    .filter(l -> !layersOut.contains(l))
+                    .map(Layer::getName)
+                    .collect(Collectors.toList());
+
+            if (!missingLayers.isEmpty() &&
+                    !ConditionalOptionPaneUtil.showConfirmationDialog(
+                            "savesession_layerremoved",
+                            null,
+                            new JLabel("<html>"
+                                    + trn("The following layer has been removed since the session was last saved:",
+                                          "The following layers have been removed since the session was last saved:", missingLayers.size())
+                                    + "<ul><li>"
+                                    + String.join("<li>", missingLayers)
+                                    + "</ul><br>"
+                                    + tr("You are about to overwrite the session file \"{0}\". Would you like to proceed?", fn)),
+                            tr("Layers removed"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE,
+                            JOptionPane.OK_OPTION)) {
+                throw new UserCancelException();
+            }
+        }
+        setCurrentLayers(layersOut);
+
+
+        if (fn.indexOf('.') == -1) {
+            sessionFile = new File(sessionFile.getPath() + (isZipSessionFile ? ".joz" : ".jos"));
+            if (!SaveActionBase.confirmOverwrite(sessionFile)) {
+                throw new UserCancelException();
+            }
+        }
+
+        Stream<Layer> layersToSaveStream = layersOut.stream()
+                .filter(layer -> layer.isSavable()
+                        && layer instanceof AbstractModifiableLayer
+                        && ((AbstractModifiableLayer) layer).requiresSaveToFile()
+                        && exporters.get(layer) != null
+                        && !exporters.get(layer).requiresZip());
+
+        boolean success = true;
+        if (forceSaveAll || SAVE_LOCAL_FILES_PROPERTY.get()) {
+            // individual files must be saved before the session file as the location may change
+            if (layersToSaveStream
+                .map(layer -> SaveAction.getInstance().doSave(layer, true))
+                .collect(Collectors.toList()) // force evaluation of all elements
+                .contains(false)) {
+
+                new Notification(tr("Not all local files referenced by the session file could be saved."
+                        + "<br>Make sure you save them before closing JOSM."))
+                    .setIcon(JOptionPane.WARNING_MESSAGE)
+                    .setDuration(Notification.TIME_LONG)
+                    .show();
+                success = false;
+            }
+        } else if (layersToSaveStream.anyMatch(l -> true)) {
+            new Notification(tr("Not all local files referenced by the session file are saved yet."
+                    + "<br>Make sure you save them before closing JOSM."))
+                .setIcon(JOptionPane.INFORMATION_MESSAGE)
+                .setDuration(Notification.TIME_LONG)
+                .show();
+        }
+
+        int active = -1;
+        Layer activeLayer = getLayerManager().getActiveLayer();
+        if (activeLayer != null) {
+            active = layersOut.indexOf(activeLayer);
+        }
+
+        SessionWriter sw = new SessionWriter(layersOut, active, exporters, dependencies, isZipSessionFile);
+        try {
+            Notification savingNotification = showSavingNotification(sessionFile.getName());
+            sw.write(sessionFile);
+            SaveActionBase.addToFileOpenHistory(sessionFile);
+            if (removeFileOnSuccess != null) {
+                PreferencesUtils.removeFromList(Config.getPref(), "file-open.history", removeFileOnSuccess.getCanonicalPath());
+                removeFileOnSuccess.delete();
+                removeFileOnSuccess = null;
+            }
+            showSavedNotification(savingNotification, sessionFile.getName());
+        } catch (IOException ex) {
+            Logging.error(ex);
+            HelpAwareOptionPane.showMessageDialogInEDT(
+                    MainApplication.getMainFrame(),
+                    tr("<html>Could not save session file ''{0}''.<br>Error is:<br>{1}</html>",
+                            sessionFile.getName(), Utils.escapeReservedCharactersHTML(ex.getMessage())),
+                    tr("IO Error"),
+                    JOptionPane.ERROR_MESSAGE,
+                    null
+            );
+            success = false;
+        }
+        return success;
+    }
+
+    /**
+     * Sets the current session file. Asks the user if necessary
+     * @param saveAs alwas ask the user
+     * @param zipRequired zip
+     * @return if the user was asked
+     * @throws UserCancelException when the user has cancelled the save process
+     */
+    protected boolean doGetFile(boolean saveAs, boolean zipRequired) throws UserCancelException {
+        if (!saveAs && sessionFile != null) {
+
+            if (isZipSessionFile || !zipRequired)
+                return true;
+
+            Logging.info("Converting *.jos to *.joz because a new layer has been added that requires zip format");
+            String oldPath = sessionFile.getAbsolutePath();
+            int i = oldPath.lastIndexOf('.');
+            File jozFile = new File(i < 0 ? oldPath : oldPath.substring(0, i) + ".joz");
+            if (!jozFile.exists()) {
+                removeFileOnSuccess = sessionFile;
+                setCurrentSession(jozFile, true);
+                return true;
+            }
+            Logging.warn("Asking user to choose a new location for the *.joz file because it already exists");
+        }
+
+        doGetFileChooser(zipRequired);
+        return false;
+    }
+
+    protected void doGetFileChooser(boolean zipRequired) throws UserCancelException {
+        AbstractFileChooser fc;
+
+        if (zipRequired) {
+            fc = createAndOpenFileChooser(false, false, tr("Save Session"), joz, JFileChooser.FILES_ONLY, "lastDirectory");
+        } else {
+            fc = createAndOpenFileChooser(false, false, tr("Save Session"), Arrays.asList(jos, joz), jos,
+                    JFileChooser.FILES_ONLY, "lastDirectory");
+        }
+
+        if (fc == null) {
+            throw new UserCancelException();
+        }
+
+        File f = fc.getSelectedFile();
+        FileFilter ff = fc.getFileFilter();
+        boolean zip;
+
+        if (zipRequired || joz.equals(ff)) {
+            zip = true;
+        } else if (jos.equals(ff)) {
+            zip = false;
+        } else {
+            zip = Utils.hasExtension(f.getName(), "joz");
+        }
+        setCurrentSession(f, zip);
+    }
+
+    /**
+     * The "Save Session" dialog
+     */
+    public class SessionSaveAsDialog extends ExtendedDialog {
+
+        /**
+         * Constructs a new {@code SessionSaveAsDialog}.
+         */
+        public SessionSaveAsDialog() {
+            super(MainApplication.getMainFrame(), tr("Save Session"), tr("Save As"), tr("Cancel"));
+            configureContextsensitiveHelp("Action/SessionSaveAs", true /* show help button */);
+            initialize();
+            setButtonIcons("save_as", "cancel");
+            setDefaultButton(1);
+            setRememberWindowGeometry(getClass().getName() + ".geometry",
+                    WindowGeometry.centerInWindow(MainApplication.getMainFrame(), new Dimension(450, 450)));
+            setContent(build(), false);
+        }
+
+        /**
+         * Initializes action.
+         */
+        public final void initialize() {
+            layers = new ArrayList<>(getLayerManager().getLayers());
+            exporters = new HashMap<>();
+            dependencies = new MultiMap<>();
+
+            Set<Layer> noExporter = new HashSet<>();
+
+            for (Layer layer : layers) {
+                SessionLayerExporter exporter = null;
+                try {
+                    exporter = SessionWriter.getSessionLayerExporter(layer);
+                } catch (IllegalArgumentException | JosmRuntimeException e) {
+                    Logging.error(e);
+                }
+                if (exporter != null) {
+                    exporters.put(layer, exporter);
+                    Collection<Layer> deps = exporter.getDependencies();
+                    if (deps != null) {
+                        dependencies.putAll(layer, deps);
+                    } else {
+                        dependencies.putVoid(layer);
+                    }
+                } else {
+                    noExporter.add(layer);
+                    exporters.put(layer, null);
+                }
+            }
+
+            int numNoExporter = 0;
+            WHILE: while (numNoExporter != noExporter.size()) {
+                numNoExporter = noExporter.size();
+                for (Layer layer : layers) {
+                    if (noExporter.contains(layer)) continue;
+                    for (Layer depLayer : dependencies.get(layer)) {
+                        if (noExporter.contains(depLayer)) {
+                            noExporter.add(layer);
+                            exporters.put(layer, null);
+                            break WHILE;
+                        }
+                    }
+                }
+            }
+        }
+
+        protected final Component build() {
+            JPanel op = new JPanel(new GridBagLayout());
+            JPanel ip = new JPanel(new GridBagLayout());
+            for (Layer layer : layers) {
+                Component exportPanel;
+                SessionLayerExporter exporter = exporters.get(layer);
+                if (exporter == null) {
+                    if (!exporters.containsKey(layer)) throw new AssertionError();
+                    exportPanel = getDisabledExportPanel(layer);
+                } else {
+                    exportPanel = exporter.getExportPanel();
+                }
+                if (exportPanel == null) continue;
+                JPanel wrapper = new JPanel(new GridBagLayout());
+                wrapper.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
+                wrapper.add(exportPanel, GBC.std().fill(GBC.HORIZONTAL));
+                ip.add(wrapper, GBC.eol().fill(GBC.HORIZONTAL).insets(2, 2, 4, 2));
+            }
+            ip.add(GBC.glue(0, 1), GBC.eol().fill(GBC.VERTICAL));
+            JScrollPane sp = new JScrollPane(ip);
+            sp.setBorder(BorderFactory.createEmptyBorder());
+            JPanel p = new JPanel(new GridBagLayout());
+            p.add(sp, GBC.eol().fill());
+            final JTabbedPane tabs = new JTabbedPane();
+            tabs.addTab(tr("Layers"), p);
+            op.add(tabs, GBC.eol().fill());
+            JCheckBox chkSaveLocal = new JCheckBox(tr("Save all local files to disk"), SAVE_LOCAL_FILES_PROPERTY.get());
+            chkSaveLocal.addChangeListener(l -> {
+                SAVE_LOCAL_FILES_PROPERTY.put(chkSaveLocal.isSelected());
+            });
+            op.add(chkSaveLocal);
+            return op;
+        }
+
+        protected final Component getDisabledExportPanel(Layer layer) {
+            JPanel p = new JPanel(new GridBagLayout());
+            JCheckBox include = new JCheckBox();
+            include.setEnabled(false);
+            JLabel lbl = new JLabel(layer.getName(), layer.getIcon(), SwingConstants.LEADING);
+            lbl.setToolTipText(tr("No exporter for this layer"));
+            lbl.setLabelFor(include);
+            lbl.setEnabled(false);
+            p.add(include, GBC.std());
+            p.add(lbl, GBC.std());
+            p.add(GBC.glue(1, 0), GBC.std().fill(GBC.HORIZONTAL));
+            return p;
+        }
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        setEnabled(MainApplication.isDisplayingMapView());
+    }
+
+    @Override
+    public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
+        updateEnabledState();
+    }
+
+    @Override
+    public void layerAdded(LayerAddEvent e) {
+        // not used
+    }
+
+    @Override
+    public void layerRemoving(LayerRemoveEvent e) {
+        if (e.isLastLayer()) { //TODO CHECK
+            setCurrentSession(null, false);
+        }
+    }
+
+    @Override
+    public void layerOrderChanged(LayerOrderChangeEvent e) {
+        // not used
+    }
+
+    /**
+     * Sets the current session file and the layers included in that file
+     * @param file file
+     * @param zip if it is a zip session file
+     * @param layers layers that are currently represented in the session file
+     */
+    public static void setCurrentSession(File file, boolean zip, List<Layer> layers) {
+        setCurrentLayers(layers);
+        setCurrentSession(file, zip);
+    }
+
+    /**
+     * Sets the current session file
+     * @param file file
+     * @param zip if it is a zip session file
+     */
+    public static void setCurrentSession(File file, boolean zip) {
+        sessionFile = file;
+        isZipSessionFile = zip;
+        if (file == null) {
+            tooltip = TOOLTIP_DEFAULT;
+        } else {
+            tooltip = tr("Save the current session file \"{0}\".", file.getName());
+        }
+        getInstance().setTooltip(tooltip);
+    }
+
+    /**
+     * Sets the layers that are currently represented in the session file
+     * @param layers layers
+     */
+    public static void setCurrentLayers(List<Layer> layers) {
+        layersInSessionFile = layers.stream()
+                .filter(l -> l instanceof AbstractModifiableLayer)
+                .map(WeakReference::new)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * Returns the tooltip for the component
+     * @return the tooltip for the component
+     */
+    public static String getTooltip() {
+        return tooltip;
+    }
+
+}
Index: src/org/openstreetmap/josm/actions/SessionSaveAsAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/SessionSaveAsAction.java	(revision 18392)
+++ src/org/openstreetmap/josm/actions/SessionSaveAsAction.java	(working copy)
@@ -4,66 +4,19 @@
 import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
 import static org.openstreetmap.josm.tools.I18n.tr;
 
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.GridBagLayout;
 import java.awt.event.ActionEvent;
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import javax.swing.BorderFactory;
-import javax.swing.JCheckBox;
-import javax.swing.JFileChooser;
-import javax.swing.JLabel;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JTabbedPane;
-import javax.swing.SwingConstants;
-import javax.swing.border.EtchedBorder;
-import javax.swing.filechooser.FileFilter;
+import java.awt.event.KeyEvent;
 
-import org.openstreetmap.josm.data.preferences.BooleanProperty;
-import org.openstreetmap.josm.gui.ExtendedDialog;
-import org.openstreetmap.josm.gui.HelpAwareOptionPane;
 import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.gui.MapFrame;
-import org.openstreetmap.josm.gui.MapFrameListener;
-import org.openstreetmap.josm.gui.Notification;
-import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
-import org.openstreetmap.josm.gui.layer.Layer;
-import org.openstreetmap.josm.gui.util.WindowGeometry;
-import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
-import org.openstreetmap.josm.io.session.SessionLayerExporter;
-import org.openstreetmap.josm.io.session.SessionWriter;
-import org.openstreetmap.josm.tools.GBC;
-import org.openstreetmap.josm.tools.JosmRuntimeException;
 import org.openstreetmap.josm.tools.Logging;
-import org.openstreetmap.josm.tools.MultiMap;
+import org.openstreetmap.josm.tools.Shortcut;
 import org.openstreetmap.josm.tools.UserCancelException;
-import org.openstreetmap.josm.tools.Utils;
 
 /**
- * Saves a JOSM session
+ * Saves a JOSM session to a new file
  * @since 4685
  */
-public class SessionSaveAsAction extends DiskAccessAction implements MapFrameListener {
-
-    private transient List<Layer> layers;
-    private transient Map<Layer, SessionLayerExporter> exporters;
-    private transient MultiMap<Layer, Layer> dependencies;
-
-    private static final BooleanProperty SAVE_LOCAL_FILES_PROPERTY = new BooleanProperty("session.savelocal", true);
+public class SessionSaveAsAction extends SessionSaveAction {
 
     /**
      * Constructs a new {@code SessionSaveAsAction}.
@@ -79,8 +32,12 @@
      * @param installAdapters False, if you don't want to install layer changed and selection changed adapters
      */
     protected SessionSaveAsAction(boolean toolbar, boolean installAdapters) {
+
         super(tr("Save Session As..."), "session", tr("Save the current session to a new file."),
-                null, toolbar, "save_as-session", installAdapters);
+                Shortcut.registerShortcut("system:savesessionas", tr("File: {0}", tr("Save Session As...")),
+                        KeyEvent.VK_S, Shortcut.ALT_CTRL_SHIFT),
+                toolbar, "save_as-session", installAdapters);
+
         setHelpId(ht("/Action/SessionSaveAs"));
         MainApplication.addMapFrameListener(this);
     }
@@ -88,255 +45,10 @@
     @Override
     public void actionPerformed(ActionEvent e) {
         try {
-            saveSession();
+            saveSession(true, false);
         } catch (UserCancelException ignore) {
             Logging.trace(ignore);
         }
     }
 
-    @Override
-    public void destroy() {
-        MainApplication.removeMapFrameListener(this);
-        super.destroy();
-    }
-
-    /**
-     * Attempts to save the session.
-     * @throws UserCancelException when the user has cancelled the save process.
-     * @since 8913
-     */
-    public void saveSession() throws UserCancelException {
-        if (!isEnabled()) {
-            return;
-        }
-
-        SessionSaveAsDialog dlg = new SessionSaveAsDialog();
-        dlg.showDialog();
-        if (dlg.getValue() != 1) {
-            throw new UserCancelException();
-        }
-
-        boolean zipRequired = layers.stream().map(l -> exporters.get(l))
-                .anyMatch(ex -> ex != null && ex.requiresZip());
-
-        FileFilter joz = new ExtensionFileFilter("joz", "joz", tr("Session file (archive) (*.joz)"));
-        FileFilter jos = new ExtensionFileFilter("jos", "jos", tr("Session file (*.jos)"));
-
-        AbstractFileChooser fc;
-
-        if (zipRequired) {
-            fc = createAndOpenFileChooser(false, false, tr("Save Session"), joz, JFileChooser.FILES_ONLY, "lastDirectory");
-        } else {
-            fc = createAndOpenFileChooser(false, false, tr("Save Session"), Arrays.asList(jos, joz), jos,
-                    JFileChooser.FILES_ONLY, "lastDirectory");
-        }
-
-        if (fc == null) {
-            throw new UserCancelException();
-        }
-
-        File file = fc.getSelectedFile();
-        String fn = file.getName();
-
-        boolean zip;
-        FileFilter ff = fc.getFileFilter();
-        if (zipRequired || joz.equals(ff)) {
-            zip = true;
-        } else if (jos.equals(ff)) {
-            zip = false;
-        } else {
-            if (Utils.hasExtension(fn, "joz")) {
-                zip = true;
-            } else {
-                zip = false;
-            }
-        }
-        if (fn.indexOf('.') == -1) {
-            file = new File(file.getPath() + (zip ? ".joz" : ".jos"));
-            if (!SaveActionBase.confirmOverwrite(file)) {
-                throw new UserCancelException();
-            }
-        }
-
-        // TODO: resolve dependencies for layers excluded by the user
-        List<Layer> layersOut = layers.stream()
-                .filter(layer -> exporters.get(layer) != null && exporters.get(layer).shallExport())
-                .collect(Collectors.toList());
-
-        Stream<Layer> layersToSaveStream = layersOut.stream()
-                .filter(layer -> layer.isSavable()
-                        && layer instanceof AbstractModifiableLayer
-                        && ((AbstractModifiableLayer) layer).requiresSaveToFile()
-                        && exporters.get(layer) != null
-                        && !exporters.get(layer).requiresZip());
-
-        if (SAVE_LOCAL_FILES_PROPERTY.get()) {
-            // individual files must be saved before the session file as the location may change
-            if (layersToSaveStream
-                .map(layer -> SaveAction.getInstance().doSave(layer, true))
-                .collect(Collectors.toList()) // force evaluation of all elements
-                .contains(false)) {
-
-                new Notification(tr("Not all local files referenced by the session file could be saved."
-                        + "<br>Make sure you save them before closing JOSM."))
-                    .setIcon(JOptionPane.WARNING_MESSAGE)
-                    .setDuration(Notification.TIME_LONG)
-                    .show();
-            }
-        } else if (layersToSaveStream.anyMatch(l -> true)) {
-            new Notification(tr("Not all local files referenced by the session file are saved yet."
-                    + "<br>Make sure you save them before closing JOSM."))
-                .setIcon(JOptionPane.INFORMATION_MESSAGE)
-                .setDuration(Notification.TIME_LONG)
-                .show();
-        }
-
-        int active = -1;
-        Layer activeLayer = getLayerManager().getActiveLayer();
-        if (activeLayer != null) {
-            active = layersOut.indexOf(activeLayer);
-        }
-
-        SessionWriter sw = new SessionWriter(layersOut, active, exporters, dependencies, zip);
-        try {
-            Notification savingNotification = showSavingNotification(file.getName());
-            sw.write(file);
-            SaveActionBase.addToFileOpenHistory(file);
-            showSavedNotification(savingNotification, file.getName());
-        } catch (IOException ex) {
-            Logging.error(ex);
-            HelpAwareOptionPane.showMessageDialogInEDT(
-                    MainApplication.getMainFrame(),
-                    tr("<html>Could not save session file ''{0}''.<br>Error is:<br>{1}</html>",
-                            file.getName(), Utils.escapeReservedCharactersHTML(ex.getMessage())),
-                    tr("IO Error"),
-                    JOptionPane.ERROR_MESSAGE,
-                    null
-            );
-        }
-    }
-
-    /**
-     * The "Save Session" dialog
-     */
-    public class SessionSaveAsDialog extends ExtendedDialog {
-
-        /**
-         * Constructs a new {@code SessionSaveAsDialog}.
-         */
-        public SessionSaveAsDialog() {
-            super(MainApplication.getMainFrame(), tr("Save Session"), tr("Save As"), tr("Cancel"));
-            configureContextsensitiveHelp("Action/SessionSaveAs", true /* show help button */);
-            initialize();
-            setButtonIcons("save_as", "cancel");
-            setDefaultButton(1);
-            setRememberWindowGeometry(getClass().getName() + ".geometry",
-                    WindowGeometry.centerInWindow(MainApplication.getMainFrame(), new Dimension(450, 450)));
-            setContent(build(), false);
-        }
-
-        /**
-         * Initializes action.
-         */
-        public final void initialize() {
-            layers = new ArrayList<>(getLayerManager().getLayers());
-            exporters = new HashMap<>();
-            dependencies = new MultiMap<>();
-
-            Set<Layer> noExporter = new HashSet<>();
-
-            for (Layer layer : layers) {
-                SessionLayerExporter exporter = null;
-                try {
-                    exporter = SessionWriter.getSessionLayerExporter(layer);
-                } catch (IllegalArgumentException | JosmRuntimeException e) {
-                    Logging.error(e);
-                }
-                if (exporter != null) {
-                    exporters.put(layer, exporter);
-                    Collection<Layer> deps = exporter.getDependencies();
-                    if (deps != null) {
-                        dependencies.putAll(layer, deps);
-                    } else {
-                        dependencies.putVoid(layer);
-                    }
-                } else {
-                    noExporter.add(layer);
-                    exporters.put(layer, null);
-                }
-            }
-
-            int numNoExporter = 0;
-            WHILE: while (numNoExporter != noExporter.size()) {
-                numNoExporter = noExporter.size();
-                for (Layer layer : layers) {
-                    if (noExporter.contains(layer)) continue;
-                    for (Layer depLayer : dependencies.get(layer)) {
-                        if (noExporter.contains(depLayer)) {
-                            noExporter.add(layer);
-                            exporters.put(layer, null);
-                            break WHILE;
-                        }
-                    }
-                }
-            }
-        }
-
-        protected final Component build() {
-            JPanel op = new JPanel(new GridBagLayout());
-            JPanel ip = new JPanel(new GridBagLayout());
-            for (Layer layer : layers) {
-                JPanel wrapper = new JPanel(new GridBagLayout());
-                wrapper.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
-                Component exportPanel;
-                SessionLayerExporter exporter = exporters.get(layer);
-                if (exporter == null) {
-                    if (!exporters.containsKey(layer)) throw new AssertionError();
-                    exportPanel = getDisabledExportPanel(layer);
-                } else {
-                    exportPanel = exporter.getExportPanel();
-                }
-                wrapper.add(exportPanel, GBC.std().fill(GBC.HORIZONTAL));
-                ip.add(wrapper, GBC.eol().fill(GBC.HORIZONTAL).insets(2, 2, 4, 2));
-            }
-            ip.add(GBC.glue(0, 1), GBC.eol().fill(GBC.VERTICAL));
-            JScrollPane sp = new JScrollPane(ip);
-            sp.setBorder(BorderFactory.createEmptyBorder());
-            JPanel p = new JPanel(new GridBagLayout());
-            p.add(sp, GBC.eol().fill());
-            final JTabbedPane tabs = new JTabbedPane();
-            tabs.addTab(tr("Layers"), p);
-            op.add(tabs, GBC.eol().fill());
-            JCheckBox chkSaveLocal = new JCheckBox(tr("Save all local files to disk"), SAVE_LOCAL_FILES_PROPERTY.get());
-            chkSaveLocal.addChangeListener(l -> {
-                SAVE_LOCAL_FILES_PROPERTY.put(chkSaveLocal.isSelected());
-            });
-            op.add(chkSaveLocal);
-            return op;
-        }
-
-        protected final Component getDisabledExportPanel(Layer layer) {
-            JPanel p = new JPanel(new GridBagLayout());
-            JCheckBox include = new JCheckBox();
-            include.setEnabled(false);
-            JLabel lbl = new JLabel(layer.getName(), layer.getIcon(), SwingConstants.LEADING);
-            lbl.setToolTipText(tr("No exporter for this layer"));
-            lbl.setLabelFor(include);
-            lbl.setEnabled(false);
-            p.add(include, GBC.std());
-            p.add(lbl, GBC.std());
-            p.add(GBC.glue(1, 0), GBC.std().fill(GBC.HORIZONTAL));
-            return p;
-        }
-    }
-
-    @Override
-    protected void updateEnabledState() {
-        setEnabled(MainApplication.isDisplayingMapView());
-    }
-
-    @Override
-    public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
-        updateEnabledState();
-    }
 }
Index: src/org/openstreetmap/josm/data/gpx/GpxData.java
===================================================================
--- src/org/openstreetmap/josm/data/gpx/GpxData.java	(revision 18392)
+++ src/org/openstreetmap/josm/data/gpx/GpxData.java	(working copy)
@@ -1094,8 +1094,20 @@
 
     @Override
     public void put(String key, Object value) {
+        put(key, value, true);
+    }
+
+    /**
+     * Put a key / value pair as a new attribute. Overrides key / value pair with the same key (if present).
+     * Only sets the modified state when setModified is true.
+     *
+     * @param key the key
+     * @param value the value
+     * @param setModified whether to change the modified state
+     */
+    public void put(String key, Object value, boolean setModified) {
         super.put(key, value);
-        invalidate();
+        fireInvalidate(setModified);
     }
 
     /**
@@ -1132,12 +1144,12 @@
     }
 
     private void fireInvalidate(boolean setModified) {
+        if (setModified) {
+            setModified(true);
+        }
         if (updating || initializing) {
             suppressedInvalidate = true;
         } else {
-            if (setModified) {
-                setModified(true);
-            }
             if (listeners.hasListeners()) {
                 GpxDataChangeEvent e = new GpxDataChangeEvent(this);
                 listeners.fireEvent(l -> l.gpxDataChanged(e));
@@ -1158,10 +1170,9 @@
      * @since 15496
      */
     public void endUpdate() {
-        boolean setModified = updating;
         updating = initializing = false;
         if (suppressedInvalidate) {
-            fireInvalidate(setModified);
+            fireInvalidate(false);
             suppressedInvalidate = false;
         }
     }
Index: src/org/openstreetmap/josm/data/gpx/GpxTrack.java
===================================================================
--- src/org/openstreetmap/josm/data/gpx/GpxTrack.java	(revision 18392)
+++ src/org/openstreetmap/josm/data/gpx/GpxTrack.java	(working copy)
@@ -82,18 +82,21 @@
 
     @Override
     public void setColor(Color color) {
-        setColorExtension(color);
+        setColorExtensionGPXD(color, true);
         colorCache = color;
     }
 
-    private void setColorExtension(Color color) {
+    private void setColorExtensionGPXD(Color color, boolean invalidate) {
         getExtensions().findAndRemove("gpxx", "DisplayColor");
         if (color == null) {
             getExtensions().findAndRemove("gpxd", "color");
         } else {
             getExtensions().addOrUpdate("gpxd", "color", String.format("#%02X%02X%02X", color.getRed(), color.getGreen(), color.getBlue()));
         }
-        fireInvalidate();
+        colorFormat = ColorFormat.GPXD;
+        if (invalidate) {
+            fireInvalidate();
+        }
     }
 
     @Override
@@ -167,7 +170,7 @@
                 closestGarminColorCache.put(c, colorString);
                 getExtensions().addIfNotPresent("gpxx", "TrackExtension").getExtensions().addOrUpdate("gpxx", "DisplayColor", colorString);
             } else if (cFormat == ColorFormat.GPXD) {
-                setColor(c);
+                setColorExtensionGPXD(c, false);
             }
             colorFormat = cFormat;
         }
Index: src/org/openstreetmap/josm/gui/MainMenu.java
===================================================================
--- src/org/openstreetmap/josm/gui/MainMenu.java	(revision 18392)
+++ src/org/openstreetmap/josm/gui/MainMenu.java	(working copy)
@@ -96,6 +96,7 @@
 import org.openstreetmap.josm.actions.SearchNotesDownloadAction;
 import org.openstreetmap.josm.actions.SelectAllAction;
 import org.openstreetmap.josm.actions.SelectNonBranchingWaySequencesAction;
+import org.openstreetmap.josm.actions.SessionSaveAction;
 import org.openstreetmap.josm.actions.SessionSaveAsAction;
 import org.openstreetmap.josm.actions.ShowStatusReportAction;
 import org.openstreetmap.josm.actions.SimplifyWayAction;
@@ -176,8 +177,10 @@
     public final SaveAction save = SaveAction.getInstance();
     /** File / Save As... **/
     public final SaveAsAction saveAs = SaveAsAction.getInstance();
+    /** File / Session &gt; Save Session **/
+    public SessionSaveAction sessionSave = SessionSaveAction.getInstance();
     /** File / Session &gt; Save Session As... **/
-    public SessionSaveAsAction sessionSaveAs;
+    public SessionSaveAsAction sessionSaveAs = new SessionSaveAsAction();
     /** File / Export to GPX... **/
     public final GpxExportAction gpxExport = new GpxExportAction();
     /** File / Download from OSM... **/
@@ -738,8 +741,8 @@
         fileMenu.addSeparator();
         add(fileMenu, save);
         add(fileMenu, saveAs);
-        sessionSaveAs = new SessionSaveAsAction();
-        ExpertToggleAction.addVisibilitySwitcher(fileMenu.add(sessionSaveAs));
+        add(fileMenu, sessionSave, true);
+        add(fileMenu, sessionSaveAs, true);
         add(fileMenu, gpxExport, true);
         fileMenu.addSeparator();
         add(fileMenu, download);
Index: src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java	(revision 18392)
+++ src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java	(working copy)
@@ -40,7 +40,8 @@
 import javax.swing.event.TableModelEvent;
 import javax.swing.event.TableModelListener;
 
-import org.openstreetmap.josm.actions.SessionSaveAsAction;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.actions.SessionSaveAction;
 import org.openstreetmap.josm.actions.UploadAction;
 import org.openstreetmap.josm.gui.ExceptionDialogUtil;
 import org.openstreetmap.josm.gui.MainApplication;
@@ -92,7 +93,7 @@
     private final UploadAndSaveProgressRenderer pnlUploadLayers = new UploadAndSaveProgressRenderer();
 
     private final SaveAndProceedAction saveAndProceedAction = new SaveAndProceedAction();
-    private final SaveSessionAction saveSessionAction = new SaveSessionAction();
+    private final SaveSessionButtonAction saveSessionAction = new SaveSessionButtonAction();
     private final DiscardAndProceedAction discardAndProceedAction = new DiscardAndProceedAction();
     private final CancelAction cancelAction = new CancelAction();
     private transient SaveAndUploadTask saveAndUploadTask;
@@ -432,18 +433,19 @@
         }
     }
 
-    class SaveSessionAction extends SessionSaveAsAction {
+    class SaveSessionButtonAction extends JosmAction {
 
-        SaveSessionAction() {
-            super(false, false);
+        SaveSessionButtonAction() {
+            super(tr("Save Session"), "session", SessionSaveAction.getTooltip(), null, false, null, false);
         }
 
         @Override
         public void actionPerformed(ActionEvent e) {
             try {
-                saveSession();
-                setUserAction(UserAction.PROCEED);
-                closeDialog();
+                if (SessionSaveAction.getInstance().saveSession(false, true)) {
+                    setUserAction(UserAction.PROCEED);
+                    closeDialog();
+                }
             } catch (UserCancelException ignore) {
                 Logging.trace(ignore);
             }
Index: src/org/openstreetmap/josm/gui/layer/LayerManager.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/LayerManager.java	(revision 18392)
+++ src/org/openstreetmap/josm/gui/layer/LayerManager.java	(working copy)
@@ -131,7 +131,7 @@
         LayerRemoveEvent(LayerManager source, Layer removedLayer) {
             super(source);
             this.removedLayer = removedLayer;
-            this.lastLayer = source.getLayers().size() == 1;
+            this.lastLayer = source.getLayers().isEmpty();
         }
 
         /**
Index: src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(revision 18392)
+++ src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(working copy)
@@ -758,8 +758,9 @@
      * @return GPX data
      */
     public static GpxData toGpxData(DataSet data, File file) {
-        GpxData gpxData = new GpxData();
+        GpxData gpxData = new GpxData(true);
         fillGpxData(gpxData, data, file, GpxConstants.GPX_PREFIX);
+        gpxData.endUpdate();
         return gpxData;
     }
 
@@ -1010,11 +1011,13 @@
 
         @Override
         public void actionPerformed(ActionEvent e) {
+            String name = getName().replaceAll("^" + tr("Converted from: {0}", ""), "");
             final GpxData gpxData = toGpxData();
-            final GpxLayer gpxLayer = new GpxLayer(gpxData, tr("Converted from: {0}", getName()));
+            final GpxLayer gpxLayer = new GpxLayer(gpxData, tr("Converted from: {0}", name), true);
             if (getAssociatedFile() != null) {
                 String filename = getAssociatedFile().getName().replaceAll(Pattern.quote(".gpx.osm") + '$', "") + ".gpx";
                 gpxLayer.setAssociatedFile(new File(getAssociatedFile().getParentFile(), filename));
+                gpxLayer.getGpxData().setModified(true);
             }
             MainApplication.getLayerManager().addLayer(gpxLayer, false);
             if (Config.getPref().getBoolean("marker.makeautomarkers", true) && !gpxData.waypoints.isEmpty()) {
Index: src/org/openstreetmap/josm/gui/layer/gpx/ConvertToDataLayerAction.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/gpx/ConvertToDataLayerAction.java	(revision 18392)
+++ src/org/openstreetmap/josm/gui/layer/gpx/ConvertToDataLayerAction.java	(working copy)
@@ -71,7 +71,8 @@
             if (err > 0) {
                 SimplifyWayAction.simplifyWays(ways, err);
             }
-            final OsmDataLayer osmLayer = new OsmDataLayer(ds, tr("Converted from: {0}", layer.getName()), null);
+            String name = layer.getName().replaceAll("^" + tr("Converted from: {0}", ""), "");
+            final OsmDataLayer osmLayer = new OsmDataLayer(ds, tr("Converted from: {0}", name), null);
             if (layer.getAssociatedFile() != null) {
                 osmLayer.setAssociatedFile(new File(layer.getAssociatedFile().getParentFile(),
                         layer.getAssociatedFile().getName() + ".osm"));
Index: src/org/openstreetmap/josm/io/GpxWriter.java
===================================================================
--- src/org/openstreetmap/josm/io/GpxWriter.java	(revision 18392)
+++ src/org/openstreetmap/josm/io/GpxWriter.java	(working copy)
@@ -123,7 +123,7 @@
                 e.put("value", entry.getValue());
             });
         }
-        data.put(META_TIME, (metaTime != null ? metaTime : Instant.now()).toString());
+        data.put(META_TIME, (metaTime != null ? metaTime : Instant.now()).toString(), false);
         data.endUpdate();
 
         Collection<IWithAttributes> all = new ArrayList<>();
Index: src/org/openstreetmap/josm/io/session/GenericSessionExporter.java
===================================================================
--- src/org/openstreetmap/josm/io/session/GenericSessionExporter.java	(revision 18392)
+++ src/org/openstreetmap/josm/io/session/GenericSessionExporter.java	(working copy)
@@ -191,6 +191,10 @@
             String zipPath = "layers/" + String.format("%02d", support.getLayerIndex()) + "/data." + extension;
             file.appendChild(support.createTextNode(zipPath));
             addDataFile(support.getOutputStreamZip(zipPath));
+            layer.setAssociatedFile(null);
+            if (layer instanceof AbstractModifiableLayer) {
+                ((AbstractModifiableLayer) layer).onPostSaveToFile();
+            }
         } else {
             try {
                 File f = layer.getAssociatedFile();
Index: src/org/openstreetmap/josm/io/session/GpxTracksSessionExporter.java
===================================================================
--- src/org/openstreetmap/josm/io/session/GpxTracksSessionExporter.java	(revision 18392)
+++ src/org/openstreetmap/josm/io/session/GpxTracksSessionExporter.java	(working copy)
@@ -1,6 +1,9 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.io.session;
 
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.IOException;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
@@ -8,8 +11,14 @@
 import java.nio.charset.StandardCharsets;
 import java.time.Instant;
 
+import javax.swing.JCheckBox;
+import javax.swing.JPanel;
+
 import org.openstreetmap.josm.gui.layer.GpxLayer;
 import org.openstreetmap.josm.io.GpxWriter;
+import org.openstreetmap.josm.io.session.SessionWriter.ExportSupport;
+import org.openstreetmap.josm.tools.GBC;
+import org.w3c.dom.Element;
 
 /**
  * Session exporter for {@link GpxLayer}.
@@ -18,6 +27,8 @@
 public class GpxTracksSessionExporter extends GenericSessionExporter<GpxLayer> {
 
     private Instant metaTime;
+    private JCheckBox chkMarkers;
+    private boolean hasMarkerLayer;
 
     /**
      * Constructs a new {@code GpxTracksSessionExporter}.
@@ -32,6 +43,38 @@
         if (layer.data == null) {
             throw new IllegalArgumentException("GPX layer without data: " + layer);
         }
+
+        hasMarkerLayer = layer.getLinkedMarkerLayer() != null
+                && layer.getLinkedMarkerLayer().data != null
+                && !layer.getLinkedMarkerLayer().data.isEmpty();
+    }
+
+    @Override
+    public JPanel getExportPanel() {
+        JPanel p = super.getExportPanel();
+        if (hasMarkerLayer) {
+            chkMarkers = new JCheckBox();
+            chkMarkers.setText(tr("include marker layer \"{0}\"", layer.getLinkedMarkerLayer().getName()));
+            chkMarkers.setSelected(true);
+            p.add(chkMarkers, GBC.eol().insets(12, 0, 0, 5));
+        }
+        return p;
+    }
+
+    @Override
+    public Element export(ExportSupport support) throws IOException {
+        Element el = super.export(support);
+        if (hasMarkerLayer && (chkMarkers == null || chkMarkers.isSelected())) {
+            Element markerEl = support.createElement("markerLayer");
+            markerEl.setAttribute("index", Integer.toString(support.getLayerIndexOf(layer.getLinkedMarkerLayer())));
+            markerEl.setAttribute("name", layer.getLinkedMarkerLayer().getName());
+            markerEl.setAttribute("visible", Boolean.toString(layer.getLinkedMarkerLayer().isVisible()));
+            if (layer.getLinkedMarkerLayer().getOpacity() != 1) {
+                markerEl.setAttribute("opacity", Double.toString(layer.getLinkedMarkerLayer().getOpacity()));
+            }
+            el.appendChild(markerEl);
+        }
+        return el;
     }
 
     @Override
Index: src/org/openstreetmap/josm/io/session/GpxTracksSessionImporter.java
===================================================================
--- src/org/openstreetmap/josm/io/session/GpxTracksSessionImporter.java	(revision 18392)
+++ src/org/openstreetmap/josm/io/session/GpxTracksSessionImporter.java	(working copy)
@@ -19,8 +19,11 @@
 import org.openstreetmap.josm.gui.layer.Layer;
 import org.openstreetmap.josm.gui.progress.ProgressMonitor;
 import org.openstreetmap.josm.io.IllegalDataException;
+import org.openstreetmap.josm.tools.Logging;
 import org.openstreetmap.josm.tools.Utils;
 import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
 
 /**
  * Session exporter for {@link GpxLayer}.
@@ -57,6 +60,16 @@
                 if (importData.getGpxLayer() != null && importData.getGpxLayer().data != null) {
                     importData.getGpxLayer().data.fromSession = true;
                 }
+                NodeList markerNodes = elem.getElementsByTagName("markerLayer");
+                if (markerNodes.getLength() > 0 && markerNodes.item(0).getNodeType() == Node.ELEMENT_NODE) {
+                    Element markerEl = (Element) markerNodes.item(0);
+                    try {
+                        int index = Integer.parseInt(markerEl.getAttribute("index"));
+                        support.addSubLayer(index, importData.getMarkerLayer(), markerEl);
+                    } catch (NumberFormatException ex) {
+                        Logging.warn(ex);
+                    }
+                }
 
                 support.addPostLayersTask(importData.getPostLayerTask());
                 return getLayer(importData);
Index: src/org/openstreetmap/josm/io/session/MarkerSessionExporter.java
===================================================================
--- src/org/openstreetmap/josm/io/session/MarkerSessionExporter.java	(revision 18392)
+++ src/org/openstreetmap/josm/io/session/MarkerSessionExporter.java	(working copy)
@@ -34,6 +34,7 @@
 public class MarkerSessionExporter extends AbstractSessionExporter<MarkerLayer> {
 
     private Instant metaTime;
+    private boolean canExport = true;
 
     /**
      * Constructs a new {@code MarkerSessionExporter}.
@@ -53,8 +54,12 @@
 
     @Override
     public Component getExportPanel() {
+        export.setSelected(true); //true even when not shown to the user as the index should be reserved for the corresponding GPX layer
+        if (layer.fromLayer != null && layer.fromLayer.getData() != null) {
+            canExport = false;
+            return null;
+        }
         final JPanel p = new JPanel(new GridBagLayout());
-        export.setSelected(true);
         final JLabel lbl = new JLabel(layer.getName(), layer.getIcon(), SwingConstants.LEADING);
         lbl.setToolTipText(layer.getToolTipText());
         lbl.setLabelFor(export);
@@ -66,11 +71,13 @@
 
     @Override
     public boolean requiresZip() {
-        return true;
+        return canExport;
     }
 
     @Override
     public Element export(ExportSupport support) throws IOException {
+        if (!canExport) return null;
+
         Element layerEl = support.createElement("layer");
         layerEl.setAttribute("type", "markers");
         layerEl.setAttribute("version", "0.1");
Index: src/org/openstreetmap/josm/io/session/MarkerSessionImporter.java
===================================================================
--- src/org/openstreetmap/josm/io/session/MarkerSessionImporter.java	(revision 18392)
+++ src/org/openstreetmap/josm/io/session/MarkerSessionImporter.java	(working copy)
@@ -48,6 +48,7 @@
 
                 support.addPostLayersTask(importData.getPostLayerTask());
 
+                importData.getGpxLayer().destroy();
                 return importData.getMarkerLayer();
             }
         } catch (XPathExpressionException e) {
Index: src/org/openstreetmap/josm/io/session/SessionReader.java
===================================================================
--- src/org/openstreetmap/josm/io/session/SessionReader.java	(revision 18392)
+++ src/org/openstreetmap/josm/io/session/SessionReader.java	(working copy)
@@ -14,6 +14,7 @@
 import java.net.URISyntaxException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
+import java.util.AbstractMap.SimpleEntry;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -250,6 +251,7 @@
         private final String layerName;
         private final int layerIndex;
         private final List<LayerDependency> layerDependencies;
+        private Map<Integer, Entry<Layer, Element>> subLayers;
 
         /**
          * Path of the file inside the zip archive.
@@ -279,6 +281,31 @@
         }
 
         /**
+         * Add sub layers
+         * @param idx index
+         * @param layer sub layer
+         * @param el The XML element of the sub layer.
+         *           Should contain "index" and "name" attributes.
+         *           Can contain "opacity" and "visible" attributes
+         * @since xxx
+         */
+        public void addSubLayer(int idx, Layer layer, Element el) {
+            if (subLayers == null) {
+                subLayers = new HashMap<>();
+            }
+            subLayers.put(idx, new SimpleEntry<>(layer, el));
+        }
+
+        /**
+         * Returns the sub layers
+         * @return the sub layers. Can be null.
+         * @since xxx
+         */
+        public Map<Integer, Entry<Layer, Element>> getSubLayers() {
+            return subLayers;
+        }
+
+        /**
          * Return an InputStream for a URI from a .jos/.joz file.
          *
          * The following forms are supported:
@@ -506,7 +533,6 @@
         List<Integer> sorted = Utils.topologicalSort(deps);
         final Map<Integer, Layer> layersMap = new TreeMap<>(Collections.reverseOrder());
         final Map<Integer, SessionLayerImporter> importers = new HashMap<>();
-        final Map<Integer, String> names = new HashMap<>();
 
         progressMonitor.setTicksCount(sorted.size());
         LAYER: for (int idx: sorted) {
@@ -519,7 +545,6 @@
                 return;
             }
             String name = e.getAttribute("name");
-            names.put(idx, name);
             if (!e.hasAttribute("type")) {
                 error(tr("missing mandatory attribute ''type'' for element ''layer''"));
                 return;
@@ -595,30 +620,49 @@
                 }
 
                 layersMap.put(idx, layer);
+                setLayerAttributes(layer, e);
+
+                if (support.getSubLayers() != null) {
+                    support.getSubLayers().forEach((Integer markerIndex, Entry<Layer, Element> entry) -> {
+                        Layer subLayer = entry.getKey();
+                        Element subElement = entry.getValue();
+
+                        layersMap.put(markerIndex, subLayer);
+                        setLayerAttributes(subLayer, subElement);
+                    });
+                }
+
             }
             progressMonitor.worked(1);
         }
 
+
         layers = new ArrayList<>();
         for (Entry<Integer, Layer> entry : layersMap.entrySet()) {
             Layer layer = entry.getValue();
-            if (layer == null) {
-                continue;
-            }
-            Element el = elems.get(entry.getKey());
-            if (el.hasAttribute("visible")) {
-                layer.setVisible(Boolean.parseBoolean(el.getAttribute("visible")));
+            if (layer != null) {
+                layers.add(layer);
             }
-            if (el.hasAttribute("opacity")) {
-                try {
-                    double opacity = Double.parseDouble(el.getAttribute("opacity"));
-                    layer.setOpacity(opacity);
-                } catch (NumberFormatException ex) {
-                    Logging.warn(ex);
-                }
+        }
+    }
+
+    private static void setLayerAttributes(Layer layer, Element e) {
+        if (layer == null)
+            return;
+
+        if (e.hasAttribute("name")) {
+            layer.setName(e.getAttribute("name"));
+        }
+        if (e.hasAttribute("visible")) {
+            layer.setVisible(Boolean.parseBoolean(e.getAttribute("visible")));
+        }
+        if (e.hasAttribute("opacity")) {
+            try {
+                double opacity = Double.parseDouble(e.getAttribute("opacity"));
+                layer.setOpacity(opacity);
+            } catch (NumberFormatException ex) {
+                Logging.warn(ex);
             }
-            layer.setName(names.get(entry.getKey()));
-            layers.add(layer);
         }
     }
 
Index: src/org/openstreetmap/josm/io/session/SessionWriter.java
===================================================================
--- src/org/openstreetmap/josm/io/session/SessionWriter.java	(revision 18392)
+++ src/org/openstreetmap/josm/io/session/SessionWriter.java	(working copy)
@@ -174,6 +174,16 @@
         }
 
         /**
+         * Get the index of the specified layer
+         * @param layer the layer
+         * @return the index of the specified layer
+         * @since xxx
+         */
+        public int getLayerIndexOf(Layer layer) {
+            return layers.indexOf(layer) + 1;
+        }
+
+        /**
          * Create a file inside the zip archive.
          *
          * @param zipPath the path inside the zip archive, e.g. "layers/03/data.xml"
@@ -234,6 +244,7 @@
             SessionLayerExporter exporter = exporters.get(layer);
             ExportSupport support = new ExportSupport(doc, index+1);
             Element el = exporter.export(support);
+            if (el == null) continue;
             el.setAttribute("index", Integer.toString(index+1));
             el.setAttribute("name", layer.getName());
             el.setAttribute("visible", Boolean.toString(layer.isVisible()));
Index: src/org/openstreetmap/josm/tools/ListenerList.java
===================================================================
--- src/org/openstreetmap/josm/tools/ListenerList.java	(revision 18392)
+++ src/org/openstreetmap/josm/tools/ListenerList.java	(working copy)
@@ -143,7 +143,7 @@
      * @return <code>true</code> if any are registered.
      */
     public boolean hasListeners() {
-        return !listeners.isEmpty();
+        return !listeners.isEmpty() || weakListeners.stream().map(l -> l.listener.get()).anyMatch(Objects::nonNull);
     }
 
     /**
Index: test/data/sessions/gpx_markers_combined.jos
===================================================================
--- test/data/sessions/gpx_markers_combined.jos	(nonexistent)
+++ test/data/sessions/gpx_markers_combined.jos	(working copy)
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<josm-session version="0.1">
+    <viewport>
+        <center lat="0.0" lon="0.0"/>
+        <scale meter-per-pixel="10.000000"/>
+    </viewport>
+    <projection>
+        <projection-choice>
+            <id>core:mercator</id>
+            <parameters/>
+        </projection-choice>
+        <code>EPSG:3857</code>
+    </projection>
+    <layers>
+        <layer index="1" name="GPX layer name" type="tracks" version="0.1" visible="true">
+            <file>layers/01/data.gpx</file>
+            <markerLayer index="2" name="Marker layer name" opacity="0.5" visible="true"/>
+        </layer>
+    </layers>
+</josm-session>
Index: test/unit/org/openstreetmap/josm/actions/SessionSaveActionTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/actions/SessionSaveActionTest.java	(nonexistent)
+++ test/unit/org/openstreetmap/josm/actions/SessionSaveActionTest.java	(working copy)
@@ -0,0 +1,77 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.actions;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.openstreetmap.josm.TestUtils;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.io.session.SessionWriterTest;
+import org.openstreetmap.josm.testutils.JOSMTestRules;
+import org.openstreetmap.josm.testutils.mockers.JOptionPaneSimpleMocker;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * Unit tests for class {@link SessionSaveAsAction}.
+ */
+class SessionSaveActionTest {
+    /**
+     * Setup test.
+     */
+    @RegisterExtension
+    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
+    public JOSMTestRules test = new JOSMTestRules().main().projection();
+
+    /**
+     * Unit test of {@link SessionSaveAction}
+     * @throws IOException Temp file could not be created
+     */
+    @Test
+    void testSaveAction() throws IOException {
+        TestUtils.assumeWorkingJMockit();
+
+        File jos = File.createTempFile("session", ".jos");
+        File joz = new File(jos.getAbsolutePath().replaceFirst(".jos$", ".joz"));
+        assertTrue(jos.exists());
+        assertFalse(joz.exists());
+
+        String overrideStr = "javax.swing.JLabel[,0,0,0x0,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=8388608,maximumSize=,minimumSize=,"
+                + "preferredSize=,defaultIcon=,disabledIcon=,horizontalAlignment=LEADING,horizontalTextPosition=TRAILING,iconTextGap=4,"
+                + "labelFor=,text=<html>The following layer has been removed since the session was last saved:<ul><li>OSM layer name</ul>"
+                + "<br>You are about to overwrite the session file \"" + joz.getName()
+                + "\". Would you like to proceed?,verticalAlignment=CENTER,verticalTextPosition=CENTER]";
+
+        SessionSaveAction saveAction = SessionSaveAction.getInstance();
+        saveAction.setEnabled(true);
+
+        OsmDataLayer osm = SessionWriterTest.createOsmLayer();
+        GpxLayer gpx = SessionWriterTest.createGpxLayer();
+
+        JOptionPaneSimpleMocker mocker = new JOptionPaneSimpleMocker(Collections.singletonMap(overrideStr, 0));
+        SessionSaveAction.setCurrentSession(jos, false, Arrays.asList(gpx, osm)); //gpx and OSM layer
+        MainApplication.getLayerManager().addLayer(gpx); //only gpx layer
+        saveAction.actionPerformed(null); //Complain that OSM layer was removed
+        assertEquals(1, mocker.getInvocationLog().size());
+        assertFalse(jos.exists());
+        assertTrue(joz.exists()); //converted jos to joz since the session includes files
+
+        mocker = new JOptionPaneSimpleMocker(Collections.singletonMap(overrideStr, 0));
+        joz.delete();
+        saveAction.actionPerformed(null); //Do not complain about removed layers
+        assertEquals(0, mocker.getInvocationLog().size());
+        assertTrue(joz.exists());
+
+        joz.delete();
+    }
+}

Property changes on: test\unit\org\openstreetmap\josm\actions\SessionSaveActionTest.java
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
Index: test/unit/org/openstreetmap/josm/actions/SessionSaveAsActionTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/actions/SessionSaveAsActionTest.java	(revision 18392)
+++ test/unit/org/openstreetmap/josm/actions/SessionSaveAsActionTest.java	(working copy)
@@ -3,8 +3,8 @@
 
 import static org.junit.jupiter.api.Assertions.assertFalse;
 
-import org.junit.jupiter.api.extension.RegisterExtension;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
 import org.openstreetmap.josm.testutils.JOSMTestRules;
 
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
@@ -19,7 +19,7 @@
      */
     @RegisterExtension
     @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
-    public JOSMTestRules test = new JOSMTestRules();
+    public JOSMTestRules test = new JOSMTestRules().main();
 
     /**
      * Unit test of {@link SessionSaveAsAction#actionPerformed}
Index: test/unit/org/openstreetmap/josm/io/session/SessionWriterTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/io/session/SessionWriterTest.java	(revision 18392)
+++ test/unit/org/openstreetmap/josm/io/session/SessionWriterTest.java	(working copy)
@@ -50,7 +50,7 @@
 /**
  * Unit tests for Session writing.
  */
-class SessionWriterTest {
+public class SessionWriterTest {
 
     protected static final class OsmHeadlessJosExporter extends OsmDataSessionExporter {
         public OsmHeadlessJosExporter(OsmDataLayer layer) {
@@ -122,6 +122,7 @@
         }
         for (final Layer l : layers) {
             SessionLayerExporter s = SessionWriter.getSessionLayerExporter(l);
+            s.getExportPanel();
             exporters.put(l, s);
             if (s instanceof GpxTracksSessionExporter) {
                 ((GpxTracksSessionExporter) s).setMetaTime(Instant.parse("2021-10-16T18:27:12.351Z"));
@@ -153,13 +154,20 @@
         }
     }
 
-    private OsmDataLayer createOsmLayer() {
+    /**
+     * Returns OSM layer
+     * @return OSM layer
+     */
+    public static OsmDataLayer createOsmLayer() {
         OsmDataLayer layer = new OsmDataLayer(new DataSet(), "OSM layer name", null);
         layer.setAssociatedFile(new File("data.osm"));
         return layer;
     }
 
-    private GpxLayer createGpxLayer() {
+    /**Returns GPX layer
+     * @return GPX layer
+     */
+    public static GpxLayer createGpxLayer() {
         GpxData data = new GpxData();
         WayPoint wp = new WayPoint(new LatLon(42.72665, -0.00747));
         wp.setInstant(Instant.parse("2021-01-01T10:15:30.00Z"));
@@ -170,21 +178,35 @@
         return layer;
     }
 
-    private MarkerLayer createMarkerLayer(GpxLayer gpx) {
+    /**
+     * Returns MarkerLayer
+     * @param gpx linked GPX layer
+     * @return MarkerLayer
+     */
+    public static MarkerLayer createMarkerLayer(GpxLayer gpx) {
         MarkerLayer layer = new MarkerLayer(gpx.data, "Marker layer name", gpx.getAssociatedFile(), gpx);
         layer.setOpacity(0.5);
         layer.setColor(new Color(0x12345678, true));
+        gpx.setLinkedMarkerLayer(layer);
         return layer;
     }
 
-    private ImageryLayer createImageryLayer() {
+    /**
+     * Returns ImageryLayer
+     * @return ImageryLayer
+     */
+    public static ImageryLayer createImageryLayer() {
         TMSLayer layer = new TMSLayer(new ImageryInfo("the name", "http://www.url.com/"));
         layer.getDisplaySettings().setOffsetBookmark(
                 new OffsetBookmark(ProjectionRegistry.getProjection().toCode(), layer.getInfo().getId(), layer.getInfo().getName(), "", 12, 34));
         return layer;
     }
 
-    private NoteLayer createNoteLayer() {
+    /**
+     * Returns NoteLayer
+     * @return NoteLayer
+     */
+    public static NoteLayer createNoteLayer() {
         return new NoteLayer(Arrays.asList(new Note(LatLon.ZERO)), "layer name");
     }
 
@@ -249,9 +271,10 @@
     @Test
     void testWriteGpxAndMarkerJoz() throws IOException {
         GpxLayer gpx = createGpxLayer();
-        Map<String, byte[]> bytes = testWrite(Arrays.asList(gpx, createMarkerLayer(gpx)), true);
+        MarkerLayer markers = createMarkerLayer(gpx);
+        Map<String, byte[]> bytes = testWrite(Arrays.asList(gpx, markers), true);
 
-        Path path = Paths.get(TestUtils.getTestDataRoot() + "/sessions/gpx_markers.jos");
+        Path path = Paths.get(TestUtils.getTestDataRoot() + "/sessions/gpx_markers_combined.jos");
         String expected = new String(Files.readAllBytes(path), StandardCharsets.UTF_8).replace("\r", "");
         String actual = new String(bytes.get("session.jos"), StandardCharsets.UTF_8).replace("\r", "");
         assertEquals(expected, actual);
@@ -261,10 +284,27 @@
         actual = new String(bytes.get("layers/01/data.gpx"), StandardCharsets.UTF_8).replace("\r", "");
         assertEquals(expected, actual);
 
+        //Test writing when the marker layer has no corresponding GPX layer:
+        gpx.setLinkedMarkerLayer(null);
+        markers.fromLayer = null;
+        markers.data.transferLayerPrefs(gpx.data.getLayerPrefs());
+        bytes = testWrite(Arrays.asList(gpx, markers), true);
+
+        path = Paths.get(TestUtils.getTestDataRoot() + "/sessions/gpx_markers.jos");
+        expected = new String(Files.readAllBytes(path), StandardCharsets.UTF_8).replace("\r", "");
+        actual = new String(bytes.get("session.jos"), StandardCharsets.UTF_8).replace("\r", "");
+        assertEquals(expected, actual);
+
+        path = Paths.get(TestUtils.getTestDataRoot() + "/sessions/data_export.gpx");
+        expected = new String(Files.readAllBytes(path), StandardCharsets.UTF_8).replace("\r", "");
+        actual = new String(bytes.get("layers/01/data.gpx"), StandardCharsets.UTF_8).replace("\r", "");
+        assertEquals(expected, actual);
+
         path = Paths.get(TestUtils.getTestDataRoot() + "/sessions/markers.gpx");
         expected = new String(Files.readAllBytes(path), StandardCharsets.UTF_8).replace("\r", "");
         actual = new String(bytes.get("layers/02/data.gpx"), StandardCharsets.UTF_8).replace("\r", "");
         assertEquals(expected, actual);
+
     }
 
     /**
