Changeset 18466 in josm


Ignore:
Timestamp:
2022-06-06T19:07:05+02:00 (2 months ago)
Author:
taylor.smock
Message:

Fix #21813: Improve marker handling in sessions and #21923: Improve session workflow/Add "save session" (patch by Bjoeni)

  • Allow saving a previously saved session
  • Add "File" -> "Save Session"
  • Add shortcuts for saving sessions
  • Add warning if a layer in a session is being removed when saving over the session
  • Improve GPX marker handling
Location:
trunk
Files:
3 added
17 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/actions/SessionLoadAction.java

    r18211 r18466  
    206206                    viewport = reader.getViewport();
    207207                    projectionChoice = reader.getProjectionChoice();
     208                    SessionSaveAction.setCurrentSession(file, zip, reader.getLayers());
    208209                } finally {
    209210                    if (tempFile) {
  • trunk/src/org/openstreetmap/josm/actions/SessionSaveAsAction.java

    r18406 r18466  
    55import static org.openstreetmap.josm.tools.I18n.tr;
    66
    7 import java.awt.Component;
    8 import java.awt.Dimension;
    9 import java.awt.GridBagLayout;
    107import java.awt.event.ActionEvent;
    11 import java.io.File;
    12 import java.io.IOException;
    13 import java.util.ArrayList;
    14 import java.util.Arrays;
    15 import java.util.Collection;
    16 import java.util.HashMap;
    17 import java.util.HashSet;
    18 import java.util.List;
    19 import java.util.Map;
    20 import java.util.Set;
    21 import java.util.stream.Collectors;
    22 import java.util.stream.Stream;
     8import java.awt.event.KeyEvent;
    239
    24 import javax.swing.BorderFactory;
    25 import javax.swing.JCheckBox;
    26 import javax.swing.JFileChooser;
    27 import javax.swing.JLabel;
    28 import javax.swing.JOptionPane;
    29 import javax.swing.JPanel;
    30 import javax.swing.JScrollPane;
    31 import javax.swing.JTabbedPane;
    32 import javax.swing.SwingConstants;
    33 import javax.swing.border.EtchedBorder;
    34 import javax.swing.filechooser.FileFilter;
    35 
    36 import org.openstreetmap.josm.data.preferences.BooleanProperty;
    37 import org.openstreetmap.josm.gui.ExtendedDialog;
    38 import org.openstreetmap.josm.gui.HelpAwareOptionPane;
    3910import org.openstreetmap.josm.gui.MainApplication;
    40 import org.openstreetmap.josm.gui.MapFrame;
    41 import org.openstreetmap.josm.gui.MapFrameListener;
    42 import org.openstreetmap.josm.gui.Notification;
    43 import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
    44 import org.openstreetmap.josm.gui.layer.Layer;
    45 import org.openstreetmap.josm.gui.util.WindowGeometry;
    46 import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
    47 import org.openstreetmap.josm.io.session.SessionLayerExporter;
    48 import org.openstreetmap.josm.io.session.SessionWriter;
    49 import org.openstreetmap.josm.tools.GBC;
    50 import org.openstreetmap.josm.tools.JosmRuntimeException;
    5111import org.openstreetmap.josm.tools.Logging;
    52 import org.openstreetmap.josm.tools.MultiMap;
     12import org.openstreetmap.josm.tools.Shortcut;
    5313import org.openstreetmap.josm.tools.UserCancelException;
    54 import org.openstreetmap.josm.tools.Utils;
    5514
    5615/**
    57  * Saves a JOSM session
     16 * Saves a JOSM session to a new file
    5817 * @since 4685
    5918 */
    60 public class SessionSaveAsAction extends DiskAccessAction implements MapFrameListener {
    61 
    62     private transient List<Layer> layers;
    63     private transient Map<Layer, SessionLayerExporter> exporters;
    64     private transient MultiMap<Layer, Layer> dependencies;
    65 
    66     private static final BooleanProperty SAVE_LOCAL_FILES_PROPERTY = new BooleanProperty("session.savelocal", true);
     19public class SessionSaveAsAction extends SessionSaveAction {
    6720
    6821    /**
     
    8033     */
    8134    protected SessionSaveAsAction(boolean toolbar, boolean installAdapters) {
     35
    8236        super(tr("Save Session As..."), "session", tr("Save the current session to a new file."),
    83                 null, toolbar, "save_as-session", installAdapters);
     37                Shortcut.registerShortcut("system:savesessionas", tr("File: {0}", tr("Save Session As...")),
     38                        KeyEvent.VK_S, Shortcut.ALT_CTRL_SHIFT),
     39                toolbar, "save_as-session", installAdapters);
     40
    8441        setHelpId(ht("/Action/SessionSaveAs"));
    85         MainApplication.addMapFrameListener(this);
    8642    }
    8743
     
    8945    public void actionPerformed(ActionEvent e) {
    9046        try {
    91             saveSession();
     47            saveSession(true, false);
    9248        } catch (UserCancelException ignore) {
    9349            Logging.trace(ignore);
     
    9652
    9753    @Override
    98     public void destroy() {
    99         MainApplication.removeMapFrameListener(this);
    100         super.destroy();
    101     }
    102 
    103     /**
    104      * Attempts to save the session.
    105      * @throws UserCancelException when the user has cancelled the save process.
    106      * @since 8913
    107      */
    108     public void saveSession() throws UserCancelException {
    109         if (!isEnabled()) {
    110             return;
    111         }
    112 
    113         SessionSaveAsDialog dlg = new SessionSaveAsDialog();
    114         dlg.showDialog();
    115         if (dlg.getValue() != 1) {
    116             throw new UserCancelException();
    117         }
    118 
    119         // TODO: resolve dependencies for layers excluded by the user
    120         List<Layer> layersOut = layers.stream()
    121                 .filter(layer -> exporters.get(layer) != null && exporters.get(layer).shallExport())
    122                 .collect(Collectors.toList());
    123 
    124         boolean zipRequired = layersOut.stream().map(exporters::get)
    125                 .anyMatch(ex -> ex != null && ex.requiresZip());
    126 
    127         FileFilter joz = new ExtensionFileFilter("joz", "joz", tr("Session file (archive) (*.joz)"));
    128         FileFilter jos = new ExtensionFileFilter("jos", "jos", tr("Session file (*.jos)"));
    129 
    130         AbstractFileChooser fc;
    131 
    132         if (zipRequired) {
    133             fc = createAndOpenFileChooser(false, false, tr("Save Session"), joz, JFileChooser.FILES_ONLY, "lastDirectory");
    134         } else {
    135             fc = createAndOpenFileChooser(false, false, tr("Save Session"), Arrays.asList(jos, joz), jos,
    136                     JFileChooser.FILES_ONLY, "lastDirectory");
    137         }
    138 
    139         if (fc == null) {
    140             throw new UserCancelException();
    141         }
    142 
    143         File file = fc.getSelectedFile();
    144         String fn = file.getName();
    145 
    146         boolean zip;
    147         FileFilter ff = fc.getFileFilter();
    148         if (zipRequired || joz.equals(ff)) {
    149             zip = true;
    150         } else if (jos.equals(ff)) {
    151             zip = false;
    152         } else {
    153             if (Utils.hasExtension(fn, "joz")) {
    154                 zip = true;
    155             } else {
    156                 zip = false;
    157             }
    158         }
    159         if (fn.indexOf('.') == -1) {
    160             file = new File(file.getPath() + (zip ? ".joz" : ".jos"));
    161             if (!SaveActionBase.confirmOverwrite(file)) {
    162                 throw new UserCancelException();
    163             }
    164         }
    165 
    166         Stream<Layer> layersToSaveStream = layersOut.stream()
    167                 .filter(layer -> layer.isSavable()
    168                         && layer instanceof AbstractModifiableLayer
    169                         && ((AbstractModifiableLayer) layer).requiresSaveToFile()
    170                         && exporters.get(layer) != null
    171                         && !exporters.get(layer).requiresZip());
    172 
    173         if (SAVE_LOCAL_FILES_PROPERTY.get()) {
    174             // individual files must be saved before the session file as the location may change
    175             if (layersToSaveStream
    176                 .map(layer -> SaveAction.getInstance().doSave(layer, true))
    177                 .collect(Collectors.toList()) // force evaluation of all elements
    178                 .contains(false)) {
    179 
    180                 new Notification(tr("Not all local files referenced by the session file could be saved."
    181                         + "<br>Make sure you save them before closing JOSM."))
    182                     .setIcon(JOptionPane.WARNING_MESSAGE)
    183                     .setDuration(Notification.TIME_LONG)
    184                     .show();
    185             }
    186         } else if (layersToSaveStream.anyMatch(l -> true)) {
    187             new Notification(tr("Not all local files referenced by the session file are saved yet."
    188                     + "<br>Make sure you save them before closing JOSM."))
    189                 .setIcon(JOptionPane.INFORMATION_MESSAGE)
    190                 .setDuration(Notification.TIME_LONG)
    191                 .show();
    192         }
    193 
    194         int active = -1;
    195         Layer activeLayer = getLayerManager().getActiveLayer();
    196         if (activeLayer != null) {
    197             active = layersOut.indexOf(activeLayer);
    198         }
    199 
    200         SessionWriter sw = new SessionWriter(layersOut, active, exporters, dependencies, zip);
    201         try {
    202             Notification savingNotification = showSavingNotification(file.getName());
    203             sw.write(file);
    204             SaveActionBase.addToFileOpenHistory(file);
    205             showSavedNotification(savingNotification, file.getName());
    206         } catch (IOException ex) {
    207             Logging.error(ex);
    208             HelpAwareOptionPane.showMessageDialogInEDT(
    209                     MainApplication.getMainFrame(),
    210                     tr("<html>Could not save session file ''{0}''.<br>Error is:<br>{1}</html>",
    211                             file.getName(), Utils.escapeReservedCharactersHTML(ex.getMessage())),
    212                     tr("IO Error"),
    213                     JOptionPane.ERROR_MESSAGE,
    214                     null
    215             );
    216         }
    217     }
    218 
    219     /**
    220      * The "Save Session" dialog
    221      */
    222     public class SessionSaveAsDialog extends ExtendedDialog {
    223 
    224         /**
    225          * Constructs a new {@code SessionSaveAsDialog}.
    226          */
    227         public SessionSaveAsDialog() {
    228             super(MainApplication.getMainFrame(), tr("Save Session"), tr("Save As"), tr("Cancel"));
    229             configureContextsensitiveHelp("Action/SessionSaveAs", true /* show help button */);
    230             initialize();
    231             setButtonIcons("save_as", "cancel");
    232             setDefaultButton(1);
    233             setRememberWindowGeometry(getClass().getName() + ".geometry",
    234                     WindowGeometry.centerInWindow(MainApplication.getMainFrame(), new Dimension(450, 450)));
    235             setContent(build(), false);
    236         }
    237 
    238         /**
    239          * Initializes action.
    240          */
    241         public final void initialize() {
    242             layers = new ArrayList<>(getLayerManager().getLayers());
    243             exporters = new HashMap<>();
    244             dependencies = new MultiMap<>();
    245 
    246             Set<Layer> noExporter = new HashSet<>();
    247 
    248             for (Layer layer : layers) {
    249                 SessionLayerExporter exporter = null;
    250                 try {
    251                     exporter = SessionWriter.getSessionLayerExporter(layer);
    252                 } catch (IllegalArgumentException | JosmRuntimeException e) {
    253                     Logging.error(e);
    254                 }
    255                 if (exporter != null) {
    256                     exporters.put(layer, exporter);
    257                     Collection<Layer> deps = exporter.getDependencies();
    258                     if (deps != null) {
    259                         dependencies.putAll(layer, deps);
    260                     } else {
    261                         dependencies.putVoid(layer);
    262                     }
    263                 } else {
    264                     noExporter.add(layer);
    265                     exporters.put(layer, null);
    266                 }
    267             }
    268 
    269             int numNoExporter = 0;
    270             WHILE: while (numNoExporter != noExporter.size()) {
    271                 numNoExporter = noExporter.size();
    272                 for (Layer layer : layers) {
    273                     if (noExporter.contains(layer)) continue;
    274                     for (Layer depLayer : dependencies.get(layer)) {
    275                         if (noExporter.contains(depLayer)) {
    276                             noExporter.add(layer);
    277                             exporters.put(layer, null);
    278                             break WHILE;
    279                         }
    280                     }
    281                 }
    282             }
    283         }
    284 
    285         protected final Component build() {
    286             JPanel op = new JPanel(new GridBagLayout());
    287             JPanel ip = new JPanel(new GridBagLayout());
    288             for (Layer layer : layers) {
    289                 JPanel wrapper = new JPanel(new GridBagLayout());
    290                 wrapper.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
    291                 Component exportPanel;
    292                 SessionLayerExporter exporter = exporters.get(layer);
    293                 if (exporter == null) {
    294                     if (!exporters.containsKey(layer)) throw new AssertionError();
    295                     exportPanel = getDisabledExportPanel(layer);
    296                 } else {
    297                     exportPanel = exporter.getExportPanel();
    298                 }
    299                 wrapper.add(exportPanel, GBC.std().fill(GBC.HORIZONTAL));
    300                 ip.add(wrapper, GBC.eol().fill(GBC.HORIZONTAL).insets(2, 2, 4, 2));
    301             }
    302             ip.add(GBC.glue(0, 1), GBC.eol().fill(GBC.VERTICAL));
    303             JScrollPane sp = new JScrollPane(ip);
    304             sp.setBorder(BorderFactory.createEmptyBorder());
    305             JPanel p = new JPanel(new GridBagLayout());
    306             p.add(sp, GBC.eol().fill());
    307             final JTabbedPane tabs = new JTabbedPane();
    308             tabs.addTab(tr("Layers"), p);
    309             op.add(tabs, GBC.eol().fill());
    310             JCheckBox chkSaveLocal = new JCheckBox(tr("Save all local files to disk"), SAVE_LOCAL_FILES_PROPERTY.get());
    311             chkSaveLocal.addChangeListener(l -> {
    312                 SAVE_LOCAL_FILES_PROPERTY.put(chkSaveLocal.isSelected());
    313             });
    314             op.add(chkSaveLocal);
    315             return op;
    316         }
    317 
    318         protected final Component getDisabledExportPanel(Layer layer) {
    319             JPanel p = new JPanel(new GridBagLayout());
    320             JCheckBox include = new JCheckBox();
    321             include.setEnabled(false);
    322             JLabel lbl = new JLabel(layer.getName(), layer.getIcon(), SwingConstants.LEADING);
    323             lbl.setToolTipText(tr("No exporter for this layer"));
    324             lbl.setLabelFor(include);
    325             lbl.setEnabled(false);
    326             p.add(include, GBC.std());
    327             p.add(lbl, GBC.std());
    328             p.add(GBC.glue(1, 0), GBC.std().fill(GBC.HORIZONTAL));
    329             return p;
    330         }
     54    protected void addListeners() {
     55        MainApplication.addMapFrameListener(this);
    33156    }
    33257
    33358    @Override
    334     protected void updateEnabledState() {
    335         setEnabled(MainApplication.isDisplayingMapView());
    336     }
    337 
    338     @Override
    339     public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
    340         updateEnabledState();
     59    protected void removeListeners() {
     60        MainApplication.removeMapFrameListener(this);
    34161    }
    34262}
  • trunk/src/org/openstreetmap/josm/gui/MainMenu.java

    r18021 r18466  
    9797import org.openstreetmap.josm.actions.SelectAllAction;
    9898import org.openstreetmap.josm.actions.SelectNonBranchingWaySequencesAction;
     99import org.openstreetmap.josm.actions.SessionSaveAction;
    99100import org.openstreetmap.josm.actions.SessionSaveAsAction;
    100101import org.openstreetmap.josm.actions.ShowStatusReportAction;
     
    177178    /** File / Save As... **/
    178179    public final SaveAsAction saveAs = SaveAsAction.getInstance();
     180    /** File / Session &gt; Save Session **/
     181    public SessionSaveAction sessionSave = SessionSaveAction.getInstance();
    179182    /** File / Session &gt; Save Session As... **/
    180     public SessionSaveAsAction sessionSaveAs;
     183    public SessionSaveAsAction sessionSaveAs = new SessionSaveAsAction();
    181184    /** File / Export to GPX... **/
    182185    public final GpxExportAction gpxExport = new GpxExportAction();
     
    739742        add(fileMenu, save);
    740743        add(fileMenu, saveAs);
    741         sessionSaveAs = new SessionSaveAsAction();
    742         ExpertToggleAction.addVisibilitySwitcher(fileMenu.add(sessionSaveAs));
     744        add(fileMenu, sessionSave, true);
     745        add(fileMenu, sessionSaveAs, true);
    743746        add(fileMenu, gpxExport, true);
    744747        fileMenu.addSeparator();
  • trunk/src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java

    r18121 r18466  
    4141import javax.swing.event.TableModelListener;
    4242
    43 import org.openstreetmap.josm.actions.SessionSaveAsAction;
     43import org.openstreetmap.josm.actions.JosmAction;
     44import org.openstreetmap.josm.actions.SessionSaveAction;
    4445import org.openstreetmap.josm.actions.UploadAction;
    4546import org.openstreetmap.josm.gui.ExceptionDialogUtil;
     
    9394
    9495    private final SaveAndProceedAction saveAndProceedAction = new SaveAndProceedAction();
    95     private final SaveSessionAction saveSessionAction = new SaveSessionAction();
     96    private final SaveSessionButtonAction saveSessionAction = new SaveSessionButtonAction();
    9697    private final DiscardAndProceedAction discardAndProceedAction = new DiscardAndProceedAction();
    9798    private final CancelAction cancelAction = new CancelAction();
     
    433434    }
    434435
    435     class SaveSessionAction extends SessionSaveAsAction {
    436 
    437         SaveSessionAction() {
    438             super(false, false);
     436    class SaveSessionButtonAction extends JosmAction {
     437
     438        SaveSessionButtonAction() {
     439            super(tr("Save Session"), "session", SessionSaveAction.getTooltip(), null, false, null, false);
    439440        }
    440441
     
    442443        public void actionPerformed(ActionEvent e) {
    443444            try {
    444                 saveSession();
    445                 setUserAction(UserAction.PROCEED);
    446                 closeDialog();
     445                if (SessionSaveAction.getInstance().saveSession(false, true)) {
     446                    setUserAction(UserAction.PROCEED);
     447                    closeDialog();
     448                }
    447449            } catch (UserCancelException ignore) {
    448450                Logging.trace(ignore);
  • trunk/src/org/openstreetmap/josm/gui/layer/LayerManager.java

    r17850 r18466  
    132132            super(source);
    133133            this.removedLayer = removedLayer;
    134             this.lastLayer = source.getLayers().size() == 1;
     134            this.lastLayer = source.getLayers().isEmpty();
    135135        }
    136136
  • trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java

    r18457 r18466  
    759759     */
    760760    public static GpxData toGpxData(DataSet data, File file) {
    761         GpxData gpxData = new GpxData();
     761        GpxData gpxData = new GpxData(true);
    762762        fillGpxData(gpxData, data, file, GpxConstants.GPX_PREFIX);
     763        gpxData.endUpdate();
    763764        return gpxData;
    764765    }
     
    10111012        @Override
    10121013        public void actionPerformed(ActionEvent e) {
     1014            String name = getName().replaceAll("^" + tr("Converted from: {0}", ""), "");
    10131015            final GpxData gpxData = toGpxData();
    1014             final GpxLayer gpxLayer = new GpxLayer(gpxData, tr("Converted from: {0}", getName()));
     1016            final GpxLayer gpxLayer = new GpxLayer(gpxData, tr("Converted from: {0}", name), true);
    10151017            if (getAssociatedFile() != null) {
    10161018                String filename = getAssociatedFile().getName().replaceAll(Pattern.quote(".gpx.osm") + '$', "") + ".gpx";
    10171019                gpxLayer.setAssociatedFile(new File(getAssociatedFile().getParentFile(), filename));
     1020                gpxLayer.getGpxData().setModified(true);
    10181021            }
    10191022            MainApplication.getLayerManager().addLayer(gpxLayer, false);
  • trunk/src/org/openstreetmap/josm/gui/layer/gpx/ConvertToDataLayerAction.java

    r18076 r18466  
    7272                SimplifyWayAction.simplifyWays(ways, err);
    7373            }
    74             final OsmDataLayer osmLayer = new OsmDataLayer(ds, tr("Converted from: {0}", layer.getName()), null);
     74            String name = layer.getName().replaceAll("^" + tr("Converted from: {0}", ""), "");
     75            final OsmDataLayer osmLayer = new OsmDataLayer(ds, tr("Converted from: {0}", name), null);
    7576            if (layer.getAssociatedFile() != null) {
    7677                osmLayer.setAssociatedFile(new File(layer.getAssociatedFile().getParentFile(),
  • trunk/src/org/openstreetmap/josm/io/session/GenericSessionExporter.java

    r17733 r18466  
    192192            file.appendChild(support.createTextNode(zipPath));
    193193            addDataFile(support.getOutputStreamZip(zipPath));
     194            layer.setAssociatedFile(null);
     195            if (layer instanceof AbstractModifiableLayer) {
     196                ((AbstractModifiableLayer) layer).onPostSaveToFile();
     197            }
    194198        } else {
    195199            try {
  • trunk/src/org/openstreetmap/josm/io/session/GpxTracksSessionExporter.java

    r18287 r18466  
    22package org.openstreetmap.josm.io.session;
    33
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.io.IOException;
    47import java.io.OutputStream;
    58import java.io.OutputStreamWriter;
     
    912import java.time.Instant;
    1013
     14import javax.swing.JCheckBox;
     15import javax.swing.JPanel;
     16
    1117import org.openstreetmap.josm.gui.layer.GpxLayer;
    1218import org.openstreetmap.josm.io.GpxWriter;
     19import org.openstreetmap.josm.io.session.SessionWriter.ExportSupport;
     20import org.openstreetmap.josm.tools.GBC;
     21import org.w3c.dom.Element;
    1322
    1423/**
     
    1928
    2029    private Instant metaTime;
     30    private JCheckBox chkMarkers;
     31    private boolean hasMarkerLayer;
    2132
    2233    /**
     
    3344            throw new IllegalArgumentException("GPX layer without data: " + layer);
    3445        }
     46
     47        hasMarkerLayer = layer.getLinkedMarkerLayer() != null
     48                && layer.getLinkedMarkerLayer().data != null
     49                && !layer.getLinkedMarkerLayer().data.isEmpty();
     50    }
     51
     52    @Override
     53    public JPanel getExportPanel() {
     54        JPanel p = super.getExportPanel();
     55        if (hasMarkerLayer) {
     56            chkMarkers = new JCheckBox();
     57            chkMarkers.setText(tr("include marker layer \"{0}\"", layer.getLinkedMarkerLayer().getName()));
     58            chkMarkers.setSelected(true);
     59            p.add(chkMarkers, GBC.eol().insets(12, 0, 0, 5));
     60        }
     61        return p;
     62    }
     63
     64    @Override
     65    public Element export(ExportSupport support) throws IOException {
     66        Element el = super.export(support);
     67        if (hasMarkerLayer && (chkMarkers == null || chkMarkers.isSelected())) {
     68            Element markerEl = support.createElement("markerLayer");
     69            markerEl.setAttribute("index", Integer.toString(support.getLayerIndexOf(layer.getLinkedMarkerLayer())));
     70            markerEl.setAttribute("name", layer.getLinkedMarkerLayer().getName());
     71            markerEl.setAttribute("visible", Boolean.toString(layer.getLinkedMarkerLayer().isVisible()));
     72            if (layer.getLinkedMarkerLayer().getOpacity() != 1) {
     73                markerEl.setAttribute("opacity", Double.toString(layer.getLinkedMarkerLayer().getOpacity()));
     74            }
     75            el.appendChild(markerEl);
     76        }
     77        return el;
    3578    }
    3679
  • trunk/src/org/openstreetmap/josm/io/session/GpxTracksSessionImporter.java

    r18287 r18466  
    2020import org.openstreetmap.josm.gui.progress.ProgressMonitor;
    2121import org.openstreetmap.josm.io.IllegalDataException;
     22import org.openstreetmap.josm.tools.Logging;
    2223import org.openstreetmap.josm.tools.Utils;
    2324import org.w3c.dom.Element;
     25import org.w3c.dom.Node;
     26import org.w3c.dom.NodeList;
    2427
    2528/**
     
    5861                    importData.getGpxLayer().data.fromSession = true;
    5962                }
     63                NodeList markerNodes = elem.getElementsByTagName("markerLayer");
     64                if (markerNodes.getLength() > 0 && markerNodes.item(0).getNodeType() == Node.ELEMENT_NODE) {
     65                    Element markerEl = (Element) markerNodes.item(0);
     66                    try {
     67                        int index = Integer.parseInt(markerEl.getAttribute("index"));
     68                        support.addSubLayer(index, importData.getMarkerLayer(), markerEl);
     69                    } catch (NumberFormatException ex) {
     70                        Logging.warn(ex);
     71                    }
     72                }
    6073
    6174                support.addPostLayersTask(importData.getPostLayerTask());
  • trunk/src/org/openstreetmap/josm/io/session/MarkerSessionExporter.java

    r18287 r18466  
    3535
    3636    private Instant metaTime;
     37    private boolean canExport = true;
    3738
    3839    /**
     
    5455    @Override
    5556    public Component getExportPanel() {
     57        export.setSelected(true); //true even when not shown to the user as the index should be reserved for the corresponding GPX layer
     58        if (layer.fromLayer != null && layer.fromLayer.getData() != null) {
     59            canExport = false;
     60            return null;
     61        }
    5662        final JPanel p = new JPanel(new GridBagLayout());
    57         export.setSelected(true);
    5863        final JLabel lbl = new JLabel(layer.getName(), layer.getIcon(), SwingConstants.LEADING);
    5964        lbl.setToolTipText(layer.getToolTipText());
     
    6772    @Override
    6873    public boolean requiresZip() {
    69         return true;
     74        return canExport;
    7075    }
    7176
    7277    @Override
    7378    public Element export(ExportSupport support) throws IOException {
     79        if (!canExport) return null;
     80
    7481        Element layerEl = support.createElement("layer");
    7582        layerEl.setAttribute("type", "markers");
  • trunk/src/org/openstreetmap/josm/io/session/MarkerSessionImporter.java

    r18287 r18466  
    4949                support.addPostLayersTask(importData.getPostLayerTask());
    5050
     51                importData.getGpxLayer().destroy();
    5152                return importData.getMarkerLayer();
    5253            }
  • trunk/src/org/openstreetmap/josm/io/session/SessionReader.java

    r18287 r18466  
    1515import java.nio.charset.StandardCharsets;
    1616import java.nio.file.Files;
     17import java.util.AbstractMap.SimpleEntry;
    1718import java.util.ArrayList;
    1819import java.util.Collection;
     
    251252        private final int layerIndex;
    252253        private final List<LayerDependency> layerDependencies;
     254        private Map<Integer, Entry<Layer, Element>> subLayers;
    253255
    254256        /**
     
    277279        public void addPostLayersTask(Runnable task) {
    278280            postLoadTasks.add(task);
     281        }
     282
     283        /**
     284         * Add sub layers
     285         * @param idx index
     286         * @param layer sub layer
     287         * @param el The XML element of the sub layer.
     288         *           Should contain "index" and "name" attributes.
     289         *           Can contain "opacity" and "visible" attributes
     290         * @since 18466
     291         */
     292        public void addSubLayer(int idx, Layer layer, Element el) {
     293            if (subLayers == null) {
     294                subLayers = new HashMap<>();
     295            }
     296            subLayers.put(idx, new SimpleEntry<>(layer, el));
     297        }
     298
     299        /**
     300         * Returns the sub layers
     301         * @return the sub layers. Can be null.
     302         * @since 18466
     303         */
     304        public Map<Integer, Entry<Layer, Element>> getSubLayers() {
     305            return subLayers;
    279306        }
    280307
     
    507534        final Map<Integer, Layer> layersMap = new TreeMap<>(Collections.reverseOrder());
    508535        final Map<Integer, SessionLayerImporter> importers = new HashMap<>();
    509         final Map<Integer, String> names = new HashMap<>();
    510536
    511537        progressMonitor.setTicksCount(sorted.size());
     
    520546            }
    521547            String name = e.getAttribute("name");
    522             names.put(idx, name);
    523548            if (!e.hasAttribute("type")) {
    524549                error(tr("missing mandatory attribute ''type'' for element ''layer''"));
     
    596621
    597622                layersMap.put(idx, layer);
     623                setLayerAttributes(layer, e);
     624
     625                if (support.getSubLayers() != null) {
     626                    support.getSubLayers().forEach((Integer markerIndex, Entry<Layer, Element> entry) -> {
     627                        Layer subLayer = entry.getKey();
     628                        Element subElement = entry.getValue();
     629
     630                        layersMap.put(markerIndex, subLayer);
     631                        setLayerAttributes(subLayer, subElement);
     632                    });
     633                }
     634
    598635            }
    599636            progressMonitor.worked(1);
    600637        }
     638
    601639
    602640        layers = new ArrayList<>();
    603641        for (Entry<Integer, Layer> entry : layersMap.entrySet()) {
    604642            Layer layer = entry.getValue();
    605             if (layer == null) {
    606                 continue;
    607             }
    608             Element el = elems.get(entry.getKey());
    609             if (el.hasAttribute("visible")) {
    610                 layer.setVisible(Boolean.parseBoolean(el.getAttribute("visible")));
    611             }
    612             if (el.hasAttribute("opacity")) {
    613                 try {
    614                     double opacity = Double.parseDouble(el.getAttribute("opacity"));
    615                     layer.setOpacity(opacity);
    616                 } catch (NumberFormatException ex) {
    617                     Logging.warn(ex);
    618                 }
    619             }
    620             layer.setName(names.get(entry.getKey()));
    621             layers.add(layer);
     643            if (layer != null) {
     644                layers.add(layer);
     645            }
     646        }
     647    }
     648
     649    private static void setLayerAttributes(Layer layer, Element e) {
     650        if (layer == null)
     651            return;
     652
     653        if (e.hasAttribute("name")) {
     654            layer.setName(e.getAttribute("name"));
     655        }
     656        if (e.hasAttribute("visible")) {
     657            layer.setVisible(Boolean.parseBoolean(e.getAttribute("visible")));
     658        }
     659        if (e.hasAttribute("opacity")) {
     660            try {
     661                double opacity = Double.parseDouble(e.getAttribute("opacity"));
     662                layer.setOpacity(opacity);
     663            } catch (NumberFormatException ex) {
     664                Logging.warn(ex);
     665            }
    622666        }
    623667    }
  • trunk/src/org/openstreetmap/josm/io/session/SessionWriter.java

    r18287 r18466  
    175175
    176176        /**
     177         * Get the index of the specified layer
     178         * @param layer the layer
     179         * @return the index of the specified layer
     180         * @since 18466
     181         */
     182        public int getLayerIndexOf(Layer layer) {
     183            return layers.indexOf(layer) + 1;
     184        }
     185
     186        /**
    177187         * Create a file inside the zip archive.
    178188         *
     
    235245            ExportSupport support = new ExportSupport(doc, index+1);
    236246            Element el = exporter.export(support);
     247            if (el == null) continue;
    237248            el.setAttribute("index", Integer.toString(index+1));
    238249            el.setAttribute("name", layer.getName());
  • trunk/src/org/openstreetmap/josm/tools/ListenerList.java

    r17374 r18466  
    144144     */
    145145    public boolean hasListeners() {
    146         return !listeners.isEmpty();
     146        return !listeners.isEmpty() || weakListeners.stream().map(l -> l.listener.get()).anyMatch(Objects::nonNull);
    147147    }
    148148
  • trunk/test/unit/org/openstreetmap/josm/actions/SessionSaveAsActionTest.java

    r17275 r18466  
    44import static org.junit.jupiter.api.Assertions.assertFalse;
    55
     6import org.junit.jupiter.api.Test;
    67import org.junit.jupiter.api.extension.RegisterExtension;
    7 import org.junit.jupiter.api.Test;
    88import org.openstreetmap.josm.testutils.JOSMTestRules;
    99
     
    2020    @RegisterExtension
    2121    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
    22     public JOSMTestRules test = new JOSMTestRules();
     22    public JOSMTestRules test = new JOSMTestRules().main();
    2323
    2424    /**
  • trunk/test/unit/org/openstreetmap/josm/io/session/SessionWriterTest.java

    r18287 r18466  
    5151 * Unit tests for Session writing.
    5252 */
    53 class SessionWriterTest {
     53public class SessionWriterTest {
    5454
    5555    protected static final class OsmHeadlessJosExporter extends OsmDataSessionExporter {
     
    123123        for (final Layer l : layers) {
    124124            SessionLayerExporter s = SessionWriter.getSessionLayerExporter(l);
     125            s.getExportPanel();
    125126            exporters.put(l, s);
    126127            if (s instanceof GpxTracksSessionExporter) {
     
    154155    }
    155156
    156     private OsmDataLayer createOsmLayer() {
     157    /**
     158     * Creates an OSM layer
     159     * @return OSM layer
     160     * @since 18466
     161     */
     162    public static OsmDataLayer createOsmLayer() {
    157163        OsmDataLayer layer = new OsmDataLayer(new DataSet(), "OSM layer name", null);
    158164        layer.setAssociatedFile(new File("data.osm"));
     
    160166    }
    161167
    162     private GpxLayer createGpxLayer() {
     168    /**
     169     * Creates a GPX layer
     170     * @return GPX layer
     171     * @since 18466
     172     */
     173    public static GpxLayer createGpxLayer() {
    163174        GpxData data = new GpxData();
    164175        WayPoint wp = new WayPoint(new LatLon(42.72665, -0.00747));
     
    171182    }
    172183
    173     private MarkerLayer createMarkerLayer(GpxLayer gpx) {
     184    /**
     185     * Creates a MarkerLayer
     186     * @param gpx linked GPX layer
     187     * @return MarkerLayer
     188     * @since 18466
     189     */
     190    public static MarkerLayer createMarkerLayer(GpxLayer gpx) {
    174191        MarkerLayer layer = new MarkerLayer(gpx.data, "Marker layer name", gpx.getAssociatedFile(), gpx);
    175192        layer.setOpacity(0.5);
    176193        layer.setColor(new Color(0x12345678, true));
     194        gpx.setLinkedMarkerLayer(layer);
    177195        return layer;
    178196    }
    179197
    180     private ImageryLayer createImageryLayer() {
     198    /**
     199     * Creates an ImageryLayer
     200     * @return ImageryLayer
     201     * @since 18466
     202     */
     203    public static ImageryLayer createImageryLayer() {
    181204        TMSLayer layer = new TMSLayer(new ImageryInfo("the name", "http://www.url.com/"));
    182205        layer.getDisplaySettings().setOffsetBookmark(
     
    185208    }
    186209
    187     private NoteLayer createNoteLayer() {
     210    /**
     211     * Creates a NoteLayer
     212     * @return NoteLayer
     213     * @since 18466
     214     */
     215    public static NoteLayer createNoteLayer() {
    188216        return new NoteLayer(Arrays.asList(new Note(LatLon.ZERO)), "layer name");
    189217    }
     
    250278    void testWriteGpxAndMarkerJoz() throws IOException {
    251279        GpxLayer gpx = createGpxLayer();
    252         Map<String, byte[]> bytes = testWrite(Arrays.asList(gpx, createMarkerLayer(gpx)), true);
    253 
    254         Path path = Paths.get(TestUtils.getTestDataRoot() + "/sessions/gpx_markers.jos");
     280        MarkerLayer markers = createMarkerLayer(gpx);
     281        Map<String, byte[]> bytes = testWrite(Arrays.asList(gpx, markers), true);
     282
     283        Path path = Paths.get(TestUtils.getTestDataRoot() + "/sessions/gpx_markers_combined.jos");
    255284        String expected = new String(Files.readAllBytes(path), StandardCharsets.UTF_8).replace("\r", "");
    256285        String actual = new String(bytes.get("session.jos"), StandardCharsets.UTF_8).replace("\r", "");
     
    262291        assertEquals(expected, actual);
    263292
     293        //Test writing when the marker layer has no corresponding GPX layer:
     294        gpx.setLinkedMarkerLayer(null);
     295        markers.fromLayer = null;
     296        markers.data.transferLayerPrefs(gpx.data.getLayerPrefs());
     297        bytes = testWrite(Arrays.asList(gpx, markers), true);
     298
     299        path = Paths.get(TestUtils.getTestDataRoot() + "/sessions/gpx_markers.jos");
     300        expected = new String(Files.readAllBytes(path), StandardCharsets.UTF_8).replace("\r", "");
     301        actual = new String(bytes.get("session.jos"), StandardCharsets.UTF_8).replace("\r", "");
     302        assertEquals(expected, actual);
     303
     304        path = Paths.get(TestUtils.getTestDataRoot() + "/sessions/data_export.gpx");
     305        expected = new String(Files.readAllBytes(path), StandardCharsets.UTF_8).replace("\r", "");
     306        actual = new String(bytes.get("layers/01/data.gpx"), StandardCharsets.UTF_8).replace("\r", "");
     307        assertEquals(expected, actual);
     308
    264309        path = Paths.get(TestUtils.getTestDataRoot() + "/sessions/markers.gpx");
    265310        expected = new String(Files.readAllBytes(path), StandardCharsets.UTF_8).replace("\r", "");
    266311        actual = new String(bytes.get("layers/02/data.gpx"), StandardCharsets.UTF_8).replace("\r", "");
    267312        assertEquals(expected, actual);
     313
    268314    }
    269315
Note: See TracChangeset for help on using the changeset viewer.