Ticket #21923: all.patch
File all.patch, 71.7 KB (added by , 3 years ago) |
---|
-
src/org/openstreetmap/josm/actions/SessionLoadAction.java
205 205 postLoadTasks = reader.getPostLoadTasks(); 206 206 viewport = reader.getViewport(); 207 207 projectionChoice = reader.getProjectionChoice(); 208 SessionSaveAction.setCurrentSession(file, zip, reader.getLayers()); 208 209 } finally { 209 210 if (tempFile) { 210 211 Utils.deleteFile(file); -
src/org/openstreetmap/josm/actions/SessionSaveAction.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.actions; 3 4 import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 5 import static org.openstreetmap.josm.tools.I18n.tr; 6 import static org.openstreetmap.josm.tools.I18n.trn; 7 8 import java.awt.Component; 9 import java.awt.Dimension; 10 import java.awt.GridBagLayout; 11 import java.awt.event.ActionEvent; 12 import java.awt.event.KeyEvent; 13 import java.io.File; 14 import java.io.IOException; 15 import java.lang.ref.WeakReference; 16 import java.util.ArrayList; 17 import java.util.Arrays; 18 import java.util.Collection; 19 import java.util.HashMap; 20 import java.util.HashSet; 21 import java.util.List; 22 import java.util.Map; 23 import java.util.Objects; 24 import java.util.Set; 25 import java.util.stream.Collectors; 26 import java.util.stream.Stream; 27 28 import javax.swing.BorderFactory; 29 import javax.swing.JCheckBox; 30 import javax.swing.JFileChooser; 31 import javax.swing.JLabel; 32 import javax.swing.JOptionPane; 33 import javax.swing.JPanel; 34 import javax.swing.JScrollPane; 35 import javax.swing.JTabbedPane; 36 import javax.swing.SwingConstants; 37 import javax.swing.border.EtchedBorder; 38 import javax.swing.filechooser.FileFilter; 39 40 import org.openstreetmap.josm.data.PreferencesUtils; 41 import org.openstreetmap.josm.data.preferences.BooleanProperty; 42 import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil; 43 import org.openstreetmap.josm.gui.ExtendedDialog; 44 import org.openstreetmap.josm.gui.HelpAwareOptionPane; 45 import org.openstreetmap.josm.gui.MainApplication; 46 import org.openstreetmap.josm.gui.MapFrame; 47 import org.openstreetmap.josm.gui.MapFrameListener; 48 import org.openstreetmap.josm.gui.Notification; 49 import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer; 50 import org.openstreetmap.josm.gui.layer.Layer; 51 import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent; 52 import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener; 53 import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent; 54 import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent; 55 import org.openstreetmap.josm.gui.util.WindowGeometry; 56 import org.openstreetmap.josm.gui.widgets.AbstractFileChooser; 57 import org.openstreetmap.josm.io.session.SessionLayerExporter; 58 import org.openstreetmap.josm.io.session.SessionWriter; 59 import org.openstreetmap.josm.spi.preferences.Config; 60 import org.openstreetmap.josm.tools.GBC; 61 import org.openstreetmap.josm.tools.JosmRuntimeException; 62 import org.openstreetmap.josm.tools.Logging; 63 import org.openstreetmap.josm.tools.MultiMap; 64 import org.openstreetmap.josm.tools.Shortcut; 65 import org.openstreetmap.josm.tools.UserCancelException; 66 import org.openstreetmap.josm.tools.Utils; 67 68 /** 69 * Saves a JOSM session 70 * @since xxx 71 */ 72 public class SessionSaveAction extends DiskAccessAction implements MapFrameListener, LayerChangeListener { 73 74 private transient List<Layer> layers; 75 private transient Map<Layer, SessionLayerExporter> exporters; 76 private transient MultiMap<Layer, Layer> dependencies; 77 78 private static final BooleanProperty SAVE_LOCAL_FILES_PROPERTY = new BooleanProperty("session.savelocal", true); 79 private static final String TOOLTIP_DEFAULT = tr("Save the current session."); 80 81 protected FileFilter joz = new ExtensionFileFilter("joz", "joz", tr("Session file (archive) (*.joz)")); 82 protected FileFilter jos = new ExtensionFileFilter("jos", "jos", tr("Session file (*.jos)")); 83 84 private File removeFileOnSuccess; 85 86 private static String tooltip = TOOLTIP_DEFAULT; 87 protected static File sessionFile; 88 protected static boolean isZipSessionFile; 89 protected static List<WeakReference<Layer>> layersInSessionFile; 90 91 private static final SessionSaveAction instance = new SessionSaveAction(); 92 93 /** 94 * Returns the instance 95 * @return the instance 96 */ 97 public static final SessionSaveAction getInstance() { 98 return instance; 99 } 100 101 /** 102 * Constructs a new {@code SessionSaveAction}. 103 */ 104 public SessionSaveAction() { 105 this(true, false); 106 updateEnabledState(); 107 } 108 109 /** 110 * Constructs a new {@code SessionSaveAction}. 111 * @param toolbar Register this action for the toolbar preferences? 112 * @param installAdapters False, if you don't want to install layer changed and selection changed adapters 113 */ 114 protected SessionSaveAction(boolean toolbar, boolean installAdapters) { 115 this(tr("Save Session"), "session", TOOLTIP_DEFAULT, 116 Shortcut.registerShortcut("system:savesession", tr("File: {0}", tr("Save Session...")), KeyEvent.VK_S, Shortcut.ALT_CTRL), 117 toolbar, "save-session", installAdapters); 118 setHelpId(ht("/Action/SessionSaveAs")); 119 } 120 121 protected SessionSaveAction(String name, String iconName, String tooltip, 122 Shortcut shortcut, boolean register, String toolbarId, boolean installAdapters) { 123 124 super(name, iconName, tooltip, shortcut, register, toolbarId, installAdapters); 125 MainApplication.addMapFrameListener(this); 126 MainApplication.getLayerManager().addLayerChangeListener(this); 127 } 128 129 @Override 130 public void actionPerformed(ActionEvent e) { 131 try { 132 saveSession(false, false); 133 } catch (UserCancelException ignore) { 134 Logging.trace(ignore); 135 } 136 } 137 138 @Override 139 public void destroy() { 140 MainApplication.removeMapFrameListener(this); 141 super.destroy(); 142 } 143 144 /** 145 * Attempts to save the session. 146 * @param saveAs true shows the dialog 147 * @param forceSaveAll saves all layers 148 * @return if the session and all layers were successfully saved 149 * @throws UserCancelException when the user has cancelled the save process 150 */ 151 public boolean saveSession(boolean saveAs, boolean forceSaveAll) throws UserCancelException { 152 if (!isEnabled()) { 153 return false; 154 } 155 156 removeFileOnSuccess = null; 157 158 SessionSaveAsDialog dlg = new SessionSaveAsDialog(); 159 if (saveAs) { 160 dlg.showDialog(); 161 if (dlg.getValue() != 1) { 162 throw new UserCancelException(); 163 } 164 } 165 166 // TODO: resolve dependencies for layers excluded by the user 167 List<Layer> layersOut = layers.stream() 168 .filter(layer -> exporters.get(layer) != null && exporters.get(layer).shallExport()) 169 .collect(Collectors.toList()); 170 171 boolean zipRequired = layersOut.stream().map(l -> exporters.get(l)) 172 .anyMatch(ex -> ex != null && ex.requiresZip()); 173 174 saveAs = !doGetFile(saveAs, zipRequired); 175 176 String fn = sessionFile.getName(); 177 178 if (!saveAs && layersInSessionFile != null) { 179 List<String> missingLayers = layersInSessionFile.stream() 180 .map(WeakReference::get) 181 .filter(Objects::nonNull) 182 .filter(l -> !layersOut.contains(l)) 183 .map(Layer::getName) 184 .collect(Collectors.toList()); 185 186 if (!missingLayers.isEmpty() && 187 !ConditionalOptionPaneUtil.showConfirmationDialog( 188 "savesession_layerremoved", 189 null, 190 new JLabel("<html>" 191 + trn("The following layer has been removed since the session was last saved:", 192 "The following layers have been removed since the session was last saved:", missingLayers.size()) 193 + "<ul><li>" 194 + String.join("<li>", missingLayers) 195 + "</ul><br>" 196 + tr("You are about to overwrite the session file \"{0}\". Would you like to proceed?", fn)), 197 tr("Layers removed"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, 198 JOptionPane.OK_OPTION)) { 199 throw new UserCancelException(); 200 } 201 } 202 setCurrentLayers(layersOut); 203 204 205 if (fn.indexOf('.') == -1) { 206 sessionFile = new File(sessionFile.getPath() + (isZipSessionFile ? ".joz" : ".jos")); 207 if (!SaveActionBase.confirmOverwrite(sessionFile)) { 208 throw new UserCancelException(); 209 } 210 } 211 212 Stream<Layer> layersToSaveStream = layersOut.stream() 213 .filter(layer -> layer.isSavable() 214 && layer instanceof AbstractModifiableLayer 215 && ((AbstractModifiableLayer) layer).requiresSaveToFile() 216 && exporters.get(layer) != null 217 && !exporters.get(layer).requiresZip()); 218 219 boolean success = true; 220 if (forceSaveAll || SAVE_LOCAL_FILES_PROPERTY.get()) { 221 // individual files must be saved before the session file as the location may change 222 if (layersToSaveStream 223 .map(layer -> SaveAction.getInstance().doSave(layer, true)) 224 .collect(Collectors.toList()) // force evaluation of all elements 225 .contains(false)) { 226 227 new Notification(tr("Not all local files referenced by the session file could be saved." 228 + "<br>Make sure you save them before closing JOSM.")) 229 .setIcon(JOptionPane.WARNING_MESSAGE) 230 .setDuration(Notification.TIME_LONG) 231 .show(); 232 success = false; 233 } 234 } else if (layersToSaveStream.anyMatch(l -> true)) { 235 new Notification(tr("Not all local files referenced by the session file are saved yet." 236 + "<br>Make sure you save them before closing JOSM.")) 237 .setIcon(JOptionPane.INFORMATION_MESSAGE) 238 .setDuration(Notification.TIME_LONG) 239 .show(); 240 } 241 242 int active = -1; 243 Layer activeLayer = getLayerManager().getActiveLayer(); 244 if (activeLayer != null) { 245 active = layersOut.indexOf(activeLayer); 246 } 247 248 SessionWriter sw = new SessionWriter(layersOut, active, exporters, dependencies, isZipSessionFile); 249 try { 250 Notification savingNotification = showSavingNotification(sessionFile.getName()); 251 sw.write(sessionFile); 252 SaveActionBase.addToFileOpenHistory(sessionFile); 253 if (removeFileOnSuccess != null) { 254 PreferencesUtils.removeFromList(Config.getPref(), "file-open.history", removeFileOnSuccess.getCanonicalPath()); 255 removeFileOnSuccess.delete(); 256 removeFileOnSuccess = null; 257 } 258 showSavedNotification(savingNotification, sessionFile.getName()); 259 } catch (IOException ex) { 260 Logging.error(ex); 261 HelpAwareOptionPane.showMessageDialogInEDT( 262 MainApplication.getMainFrame(), 263 tr("<html>Could not save session file ''{0}''.<br>Error is:<br>{1}</html>", 264 sessionFile.getName(), Utils.escapeReservedCharactersHTML(ex.getMessage())), 265 tr("IO Error"), 266 JOptionPane.ERROR_MESSAGE, 267 null 268 ); 269 success = false; 270 } 271 return success; 272 } 273 274 /** 275 * Sets the current session file. Asks the user if necessary 276 * @param saveAs alwas ask the user 277 * @param zipRequired zip 278 * @return if the user was asked 279 * @throws UserCancelException when the user has cancelled the save process 280 */ 281 protected boolean doGetFile(boolean saveAs, boolean zipRequired) throws UserCancelException { 282 if (!saveAs && sessionFile != null) { 283 284 if (isZipSessionFile || !zipRequired) 285 return true; 286 287 Logging.info("Converting *.jos to *.joz because a new layer has been added that requires zip format"); 288 String oldPath = sessionFile.getAbsolutePath(); 289 int i = oldPath.lastIndexOf('.'); 290 File jozFile = new File(i < 0 ? oldPath : oldPath.substring(0, i) + ".joz"); 291 if (!jozFile.exists()) { 292 removeFileOnSuccess = sessionFile; 293 setCurrentSession(jozFile, true); 294 return true; 295 } 296 Logging.warn("Asking user to choose a new location for the *.joz file because it already exists"); 297 } 298 299 doGetFileChooser(zipRequired); 300 return false; 301 } 302 303 protected void doGetFileChooser(boolean zipRequired) throws UserCancelException { 304 AbstractFileChooser fc; 305 306 if (zipRequired) { 307 fc = createAndOpenFileChooser(false, false, tr("Save Session"), joz, JFileChooser.FILES_ONLY, "lastDirectory"); 308 } else { 309 fc = createAndOpenFileChooser(false, false, tr("Save Session"), Arrays.asList(jos, joz), jos, 310 JFileChooser.FILES_ONLY, "lastDirectory"); 311 } 312 313 if (fc == null) { 314 throw new UserCancelException(); 315 } 316 317 File f = fc.getSelectedFile(); 318 FileFilter ff = fc.getFileFilter(); 319 boolean zip; 320 321 if (zipRequired || joz.equals(ff)) { 322 zip = true; 323 } else if (jos.equals(ff)) { 324 zip = false; 325 } else { 326 zip = Utils.hasExtension(f.getName(), "joz"); 327 } 328 setCurrentSession(f, zip); 329 } 330 331 /** 332 * The "Save Session" dialog 333 */ 334 public class SessionSaveAsDialog extends ExtendedDialog { 335 336 /** 337 * Constructs a new {@code SessionSaveAsDialog}. 338 */ 339 public SessionSaveAsDialog() { 340 super(MainApplication.getMainFrame(), tr("Save Session"), tr("Save As"), tr("Cancel")); 341 configureContextsensitiveHelp("Action/SessionSaveAs", true /* show help button */); 342 initialize(); 343 setButtonIcons("save_as", "cancel"); 344 setDefaultButton(1); 345 setRememberWindowGeometry(getClass().getName() + ".geometry", 346 WindowGeometry.centerInWindow(MainApplication.getMainFrame(), new Dimension(450, 450))); 347 setContent(build(), false); 348 } 349 350 /** 351 * Initializes action. 352 */ 353 public final void initialize() { 354 layers = new ArrayList<>(getLayerManager().getLayers()); 355 exporters = new HashMap<>(); 356 dependencies = new MultiMap<>(); 357 358 Set<Layer> noExporter = new HashSet<>(); 359 360 for (Layer layer : layers) { 361 SessionLayerExporter exporter = null; 362 try { 363 exporter = SessionWriter.getSessionLayerExporter(layer); 364 } catch (IllegalArgumentException | JosmRuntimeException e) { 365 Logging.error(e); 366 } 367 if (exporter != null) { 368 exporters.put(layer, exporter); 369 Collection<Layer> deps = exporter.getDependencies(); 370 if (deps != null) { 371 dependencies.putAll(layer, deps); 372 } else { 373 dependencies.putVoid(layer); 374 } 375 } else { 376 noExporter.add(layer); 377 exporters.put(layer, null); 378 } 379 } 380 381 int numNoExporter = 0; 382 WHILE: while (numNoExporter != noExporter.size()) { 383 numNoExporter = noExporter.size(); 384 for (Layer layer : layers) { 385 if (noExporter.contains(layer)) continue; 386 for (Layer depLayer : dependencies.get(layer)) { 387 if (noExporter.contains(depLayer)) { 388 noExporter.add(layer); 389 exporters.put(layer, null); 390 break WHILE; 391 } 392 } 393 } 394 } 395 } 396 397 protected final Component build() { 398 JPanel op = new JPanel(new GridBagLayout()); 399 JPanel ip = new JPanel(new GridBagLayout()); 400 for (Layer layer : layers) { 401 Component exportPanel; 402 SessionLayerExporter exporter = exporters.get(layer); 403 if (exporter == null) { 404 if (!exporters.containsKey(layer)) throw new AssertionError(); 405 exportPanel = getDisabledExportPanel(layer); 406 } else { 407 exportPanel = exporter.getExportPanel(); 408 } 409 if (exportPanel == null) continue; 410 JPanel wrapper = new JPanel(new GridBagLayout()); 411 wrapper.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED)); 412 wrapper.add(exportPanel, GBC.std().fill(GBC.HORIZONTAL)); 413 ip.add(wrapper, GBC.eol().fill(GBC.HORIZONTAL).insets(2, 2, 4, 2)); 414 } 415 ip.add(GBC.glue(0, 1), GBC.eol().fill(GBC.VERTICAL)); 416 JScrollPane sp = new JScrollPane(ip); 417 sp.setBorder(BorderFactory.createEmptyBorder()); 418 JPanel p = new JPanel(new GridBagLayout()); 419 p.add(sp, GBC.eol().fill()); 420 final JTabbedPane tabs = new JTabbedPane(); 421 tabs.addTab(tr("Layers"), p); 422 op.add(tabs, GBC.eol().fill()); 423 JCheckBox chkSaveLocal = new JCheckBox(tr("Save all local files to disk"), SAVE_LOCAL_FILES_PROPERTY.get()); 424 chkSaveLocal.addChangeListener(l -> { 425 SAVE_LOCAL_FILES_PROPERTY.put(chkSaveLocal.isSelected()); 426 }); 427 op.add(chkSaveLocal); 428 return op; 429 } 430 431 protected final Component getDisabledExportPanel(Layer layer) { 432 JPanel p = new JPanel(new GridBagLayout()); 433 JCheckBox include = new JCheckBox(); 434 include.setEnabled(false); 435 JLabel lbl = new JLabel(layer.getName(), layer.getIcon(), SwingConstants.LEADING); 436 lbl.setToolTipText(tr("No exporter for this layer")); 437 lbl.setLabelFor(include); 438 lbl.setEnabled(false); 439 p.add(include, GBC.std()); 440 p.add(lbl, GBC.std()); 441 p.add(GBC.glue(1, 0), GBC.std().fill(GBC.HORIZONTAL)); 442 return p; 443 } 444 } 445 446 @Override 447 protected void updateEnabledState() { 448 setEnabled(MainApplication.isDisplayingMapView()); 449 } 450 451 @Override 452 public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) { 453 updateEnabledState(); 454 } 455 456 @Override 457 public void layerAdded(LayerAddEvent e) { 458 // not used 459 } 460 461 @Override 462 public void layerRemoving(LayerRemoveEvent e) { 463 if (e.isLastLayer()) { //TODO CHECK 464 setCurrentSession(null, false); 465 } 466 } 467 468 @Override 469 public void layerOrderChanged(LayerOrderChangeEvent e) { 470 // not used 471 } 472 473 /** 474 * Sets the current session file and the layers included in that file 475 * @param file file 476 * @param zip if it is a zip session file 477 * @param layers layers that are currently represented in the session file 478 */ 479 public static void setCurrentSession(File file, boolean zip, List<Layer> layers) { 480 setCurrentLayers(layers); 481 setCurrentSession(file, zip); 482 } 483 484 /** 485 * Sets the current session file 486 * @param file file 487 * @param zip if it is a zip session file 488 */ 489 public static void setCurrentSession(File file, boolean zip) { 490 sessionFile = file; 491 isZipSessionFile = zip; 492 if (file == null) { 493 tooltip = TOOLTIP_DEFAULT; 494 } else { 495 tooltip = tr("Save the current session file \"{0}\".", file.getName()); 496 } 497 getInstance().setTooltip(tooltip); 498 } 499 500 /** 501 * Sets the layers that are currently represented in the session file 502 * @param layers layers 503 */ 504 public static void setCurrentLayers(List<Layer> layers) { 505 layersInSessionFile = layers.stream() 506 .filter(l -> l instanceof AbstractModifiableLayer) 507 .map(WeakReference::new) 508 .collect(Collectors.toList()); 509 } 510 511 /** 512 * Returns the tooltip for the component 513 * @return the tooltip for the component 514 */ 515 public static String getTooltip() { 516 return tooltip; 517 } 518 519 } -
src/org/openstreetmap/josm/actions/SessionSaveAsAction.java
4 4 import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 5 5 import static org.openstreetmap.josm.tools.I18n.tr; 6 6 7 import java.awt.Component;8 import java.awt.Dimension;9 import java.awt.GridBagLayout;10 7 import 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; 23 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; 8 import java.awt.event.KeyEvent; 35 9 36 import org.openstreetmap.josm.data.preferences.BooleanProperty;37 import org.openstreetmap.josm.gui.ExtendedDialog;38 import org.openstreetmap.josm.gui.HelpAwareOptionPane;39 10 import 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;51 11 import org.openstreetmap.josm.tools.Logging; 52 import org.openstreetmap.josm.tools. MultiMap;12 import org.openstreetmap.josm.tools.Shortcut; 53 13 import org.openstreetmap.josm.tools.UserCancelException; 54 import org.openstreetmap.josm.tools.Utils;55 14 56 15 /** 57 * Saves a JOSM session 16 * Saves a JOSM session to a new file 58 17 * @since 4685 59 18 */ 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); 19 public class SessionSaveAsAction extends SessionSaveAction { 67 20 68 21 /** 69 22 * Constructs a new {@code SessionSaveAsAction}. … … 79 32 * @param installAdapters False, if you don't want to install layer changed and selection changed adapters 80 33 */ 81 34 protected SessionSaveAsAction(boolean toolbar, boolean installAdapters) { 35 82 36 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 84 41 setHelpId(ht("/Action/SessionSaveAs")); 85 42 MainApplication.addMapFrameListener(this); 86 43 } … … 88 45 @Override 89 46 public void actionPerformed(ActionEvent e) { 90 47 try { 91 saveSession( );48 saveSession(true, false); 92 49 } catch (UserCancelException ignore) { 93 50 Logging.trace(ignore); 94 51 } 95 52 } 96 53 97 @Override98 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 8913107 */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 boolean zipRequired = layers.stream().map(l -> exporters.get(l))120 .anyMatch(ex -> ex != null && ex.requiresZip());121 122 FileFilter joz = new ExtensionFileFilter("joz", "joz", tr("Session file (archive) (*.joz)"));123 FileFilter jos = new ExtensionFileFilter("jos", "jos", tr("Session file (*.jos)"));124 125 AbstractFileChooser fc;126 127 if (zipRequired) {128 fc = createAndOpenFileChooser(false, false, tr("Save Session"), joz, JFileChooser.FILES_ONLY, "lastDirectory");129 } else {130 fc = createAndOpenFileChooser(false, false, tr("Save Session"), Arrays.asList(jos, joz), jos,131 JFileChooser.FILES_ONLY, "lastDirectory");132 }133 134 if (fc == null) {135 throw new UserCancelException();136 }137 138 File file = fc.getSelectedFile();139 String fn = file.getName();140 141 boolean zip;142 FileFilter ff = fc.getFileFilter();143 if (zipRequired || joz.equals(ff)) {144 zip = true;145 } else if (jos.equals(ff)) {146 zip = false;147 } else {148 if (Utils.hasExtension(fn, "joz")) {149 zip = true;150 } else {151 zip = false;152 }153 }154 if (fn.indexOf('.') == -1) {155 file = new File(file.getPath() + (zip ? ".joz" : ".jos"));156 if (!SaveActionBase.confirmOverwrite(file)) {157 throw new UserCancelException();158 }159 }160 161 // TODO: resolve dependencies for layers excluded by the user162 List<Layer> layersOut = layers.stream()163 .filter(layer -> exporters.get(layer) != null && exporters.get(layer).shallExport())164 .collect(Collectors.toList());165 166 Stream<Layer> layersToSaveStream = layersOut.stream()167 .filter(layer -> layer.isSavable()168 && layer instanceof AbstractModifiableLayer169 && ((AbstractModifiableLayer) layer).requiresSaveToFile()170 && exporters.get(layer) != null171 && !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 change175 if (layersToSaveStream176 .map(layer -> SaveAction.getInstance().doSave(layer, true))177 .collect(Collectors.toList()) // force evaluation of all elements178 .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 null215 );216 }217 }218 219 /**220 * The "Save Session" dialog221 */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 }331 }332 333 @Override334 protected void updateEnabledState() {335 setEnabled(MainApplication.isDisplayingMapView());336 }337 338 @Override339 public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {340 updateEnabledState();341 }342 54 } -
src/org/openstreetmap/josm/data/gpx/GpxData.java
1094 1094 1095 1095 @Override 1096 1096 public void put(String key, Object value) { 1097 put(key, value, true); 1098 } 1099 1100 /** 1101 * Put a key / value pair as a new attribute. Overrides key / value pair with the same key (if present). 1102 * Only sets the modified state when setModified is true. 1103 * 1104 * @param key the key 1105 * @param value the value 1106 * @param setModified whether to change the modified state 1107 */ 1108 public void put(String key, Object value, boolean setModified) { 1097 1109 super.put(key, value); 1098 invalidate();1110 fireInvalidate(setModified); 1099 1111 } 1100 1112 1101 1113 /** … … 1132 1144 } 1133 1145 1134 1146 private void fireInvalidate(boolean setModified) { 1147 if (setModified) { 1148 setModified(true); 1149 } 1135 1150 if (updating || initializing) { 1136 1151 suppressedInvalidate = true; 1137 1152 } else { 1138 if (setModified) {1139 setModified(true);1140 }1141 1153 if (listeners.hasListeners()) { 1142 1154 GpxDataChangeEvent e = new GpxDataChangeEvent(this); 1143 1155 listeners.fireEvent(l -> l.gpxDataChanged(e)); … … 1158 1170 * @since 15496 1159 1171 */ 1160 1172 public void endUpdate() { 1161 boolean setModified = updating;1162 1173 updating = initializing = false; 1163 1174 if (suppressedInvalidate) { 1164 fireInvalidate( setModified);1175 fireInvalidate(false); 1165 1176 suppressedInvalidate = false; 1166 1177 } 1167 1178 } -
src/org/openstreetmap/josm/data/gpx/GpxTrack.java
82 82 83 83 @Override 84 84 public void setColor(Color color) { 85 setColorExtension (color);85 setColorExtensionGPXD(color, true); 86 86 colorCache = color; 87 87 } 88 88 89 private void setColorExtension (Color color) {89 private void setColorExtensionGPXD(Color color, boolean invalidate) { 90 90 getExtensions().findAndRemove("gpxx", "DisplayColor"); 91 91 if (color == null) { 92 92 getExtensions().findAndRemove("gpxd", "color"); 93 93 } else { 94 94 getExtensions().addOrUpdate("gpxd", "color", String.format("#%02X%02X%02X", color.getRed(), color.getGreen(), color.getBlue())); 95 95 } 96 fireInvalidate(); 96 colorFormat = ColorFormat.GPXD; 97 if (invalidate) { 98 fireInvalidate(); 99 } 97 100 } 98 101 99 102 @Override … … 167 170 closestGarminColorCache.put(c, colorString); 168 171 getExtensions().addIfNotPresent("gpxx", "TrackExtension").getExtensions().addOrUpdate("gpxx", "DisplayColor", colorString); 169 172 } else if (cFormat == ColorFormat.GPXD) { 170 setColor (c);173 setColorExtensionGPXD(c, false); 171 174 } 172 175 colorFormat = cFormat; 173 176 } -
src/org/openstreetmap/josm/gui/MainMenu.java
96 96 import org.openstreetmap.josm.actions.SearchNotesDownloadAction; 97 97 import org.openstreetmap.josm.actions.SelectAllAction; 98 98 import org.openstreetmap.josm.actions.SelectNonBranchingWaySequencesAction; 99 import org.openstreetmap.josm.actions.SessionSaveAction; 99 100 import org.openstreetmap.josm.actions.SessionSaveAsAction; 100 101 import org.openstreetmap.josm.actions.ShowStatusReportAction; 101 102 import org.openstreetmap.josm.actions.SimplifyWayAction; … … 176 177 public final SaveAction save = SaveAction.getInstance(); 177 178 /** File / Save As... **/ 178 179 public final SaveAsAction saveAs = SaveAsAction.getInstance(); 180 /** File / Session > Save Session **/ 181 public SessionSaveAction sessionSave = SessionSaveAction.getInstance(); 179 182 /** File / Session > Save Session As... **/ 180 public SessionSaveAsAction sessionSaveAs ;183 public SessionSaveAsAction sessionSaveAs = new SessionSaveAsAction(); 181 184 /** File / Export to GPX... **/ 182 185 public final GpxExportAction gpxExport = new GpxExportAction(); 183 186 /** File / Download from OSM... **/ … … 738 741 fileMenu.addSeparator(); 739 742 add(fileMenu, save); 740 743 add(fileMenu, saveAs); 741 sessionSaveAs = new SessionSaveAsAction();742 ExpertToggleAction.addVisibilitySwitcher(fileMenu.add(sessionSaveAs));744 add(fileMenu, sessionSave, true); 745 add(fileMenu, sessionSaveAs, true); 743 746 add(fileMenu, gpxExport, true); 744 747 fileMenu.addSeparator(); 745 748 add(fileMenu, download); -
src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java
40 40 import javax.swing.event.TableModelEvent; 41 41 import javax.swing.event.TableModelListener; 42 42 43 import org.openstreetmap.josm.actions.SessionSaveAsAction; 43 import org.openstreetmap.josm.actions.JosmAction; 44 import org.openstreetmap.josm.actions.SessionSaveAction; 44 45 import org.openstreetmap.josm.actions.UploadAction; 45 46 import org.openstreetmap.josm.gui.ExceptionDialogUtil; 46 47 import org.openstreetmap.josm.gui.MainApplication; … … 92 93 private final UploadAndSaveProgressRenderer pnlUploadLayers = new UploadAndSaveProgressRenderer(); 93 94 94 95 private final SaveAndProceedAction saveAndProceedAction = new SaveAndProceedAction(); 95 private final SaveSession Action saveSessionAction = new SaveSessionAction();96 private final SaveSessionButtonAction saveSessionAction = new SaveSessionButtonAction(); 96 97 private final DiscardAndProceedAction discardAndProceedAction = new DiscardAndProceedAction(); 97 98 private final CancelAction cancelAction = new CancelAction(); 98 99 private transient SaveAndUploadTask saveAndUploadTask; … … 432 433 } 433 434 } 434 435 435 class SaveSession Action extends SessionSaveAsAction {436 class SaveSessionButtonAction extends JosmAction { 436 437 437 SaveSession Action() {438 super( false, false);438 SaveSessionButtonAction() { 439 super(tr("Save Session"), "session", SessionSaveAction.getTooltip(), null, false, null, false); 439 440 } 440 441 441 442 @Override 442 443 public void actionPerformed(ActionEvent e) { 443 444 try { 444 saveSession(); 445 setUserAction(UserAction.PROCEED); 446 closeDialog(); 445 if (SessionSaveAction.getInstance().saveSession(false, true)) { 446 setUserAction(UserAction.PROCEED); 447 closeDialog(); 448 } 447 449 } catch (UserCancelException ignore) { 448 450 Logging.trace(ignore); 449 451 } -
src/org/openstreetmap/josm/gui/layer/LayerManager.java
131 131 LayerRemoveEvent(LayerManager source, Layer removedLayer) { 132 132 super(source); 133 133 this.removedLayer = removedLayer; 134 this.lastLayer = source.getLayers(). size() == 1;134 this.lastLayer = source.getLayers().isEmpty(); 135 135 } 136 136 137 137 /** -
src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
758 758 * @return GPX data 759 759 */ 760 760 public static GpxData toGpxData(DataSet data, File file) { 761 GpxData gpxData = new GpxData( );761 GpxData gpxData = new GpxData(true); 762 762 fillGpxData(gpxData, data, file, GpxConstants.GPX_PREFIX); 763 gpxData.endUpdate(); 763 764 return gpxData; 764 765 } 765 766 … … 1010 1011 1011 1012 @Override 1012 1013 public void actionPerformed(ActionEvent e) { 1014 String name = getName().replaceAll("^" + tr("Converted from: {0}", ""), ""); 1013 1015 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); 1015 1017 if (getAssociatedFile() != null) { 1016 1018 String filename = getAssociatedFile().getName().replaceAll(Pattern.quote(".gpx.osm") + '$', "") + ".gpx"; 1017 1019 gpxLayer.setAssociatedFile(new File(getAssociatedFile().getParentFile(), filename)); 1020 gpxLayer.getGpxData().setModified(true); 1018 1021 } 1019 1022 MainApplication.getLayerManager().addLayer(gpxLayer, false); 1020 1023 if (Config.getPref().getBoolean("marker.makeautomarkers", true) && !gpxData.waypoints.isEmpty()) { -
src/org/openstreetmap/josm/gui/layer/gpx/ConvertToDataLayerAction.java
71 71 if (err > 0) { 72 72 SimplifyWayAction.simplifyWays(ways, err); 73 73 } 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); 75 76 if (layer.getAssociatedFile() != null) { 76 77 osmLayer.setAssociatedFile(new File(layer.getAssociatedFile().getParentFile(), 77 78 layer.getAssociatedFile().getName() + ".osm")); -
src/org/openstreetmap/josm/io/GpxWriter.java
123 123 e.put("value", entry.getValue()); 124 124 }); 125 125 } 126 data.put(META_TIME, (metaTime != null ? metaTime : Instant.now()).toString() );126 data.put(META_TIME, (metaTime != null ? metaTime : Instant.now()).toString(), false); 127 127 data.endUpdate(); 128 128 129 129 Collection<IWithAttributes> all = new ArrayList<>(); -
src/org/openstreetmap/josm/io/session/GenericSessionExporter.java
191 191 String zipPath = "layers/" + String.format("%02d", support.getLayerIndex()) + "/data." + extension; 192 192 file.appendChild(support.createTextNode(zipPath)); 193 193 addDataFile(support.getOutputStreamZip(zipPath)); 194 layer.setAssociatedFile(null); 195 if (layer instanceof AbstractModifiableLayer) { 196 ((AbstractModifiableLayer) layer).onPostSaveToFile(); 197 } 194 198 } else { 195 199 try { 196 200 File f = layer.getAssociatedFile(); -
src/org/openstreetmap/josm/io/session/GpxTracksSessionExporter.java
1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.io.session; 3 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.io.IOException; 4 7 import java.io.OutputStream; 5 8 import java.io.OutputStreamWriter; 6 9 import java.io.PrintWriter; … … 8 11 import java.nio.charset.StandardCharsets; 9 12 import java.time.Instant; 10 13 14 import javax.swing.JCheckBox; 15 import javax.swing.JPanel; 16 11 17 import org.openstreetmap.josm.gui.layer.GpxLayer; 12 18 import org.openstreetmap.josm.io.GpxWriter; 19 import org.openstreetmap.josm.io.session.SessionWriter.ExportSupport; 20 import org.openstreetmap.josm.tools.GBC; 21 import org.w3c.dom.Element; 13 22 14 23 /** 15 24 * Session exporter for {@link GpxLayer}. … … 18 27 public class GpxTracksSessionExporter extends GenericSessionExporter<GpxLayer> { 19 28 20 29 private Instant metaTime; 30 private JCheckBox chkMarkers; 31 private boolean hasMarkerLayer; 21 32 22 33 /** 23 34 * Constructs a new {@code GpxTracksSessionExporter}. … … 32 43 if (layer.data == null) { 33 44 throw new IllegalArgumentException("GPX layer without data: " + layer); 34 45 } 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; 35 78 } 36 79 37 80 @Override -
src/org/openstreetmap/josm/io/session/GpxTracksSessionImporter.java
19 19 import org.openstreetmap.josm.gui.layer.Layer; 20 20 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 21 21 import org.openstreetmap.josm.io.IllegalDataException; 22 import org.openstreetmap.josm.tools.Logging; 22 23 import org.openstreetmap.josm.tools.Utils; 23 24 import org.w3c.dom.Element; 25 import org.w3c.dom.Node; 26 import org.w3c.dom.NodeList; 24 27 25 28 /** 26 29 * Session exporter for {@link GpxLayer}. … … 57 60 if (importData.getGpxLayer() != null && importData.getGpxLayer().data != null) { 58 61 importData.getGpxLayer().data.fromSession = true; 59 62 } 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 } 60 73 61 74 support.addPostLayersTask(importData.getPostLayerTask()); 62 75 return getLayer(importData); -
src/org/openstreetmap/josm/io/session/MarkerSessionExporter.java
34 34 public class MarkerSessionExporter extends AbstractSessionExporter<MarkerLayer> { 35 35 36 36 private Instant metaTime; 37 private boolean canExport = true; 37 38 38 39 /** 39 40 * Constructs a new {@code MarkerSessionExporter}. … … 53 54 54 55 @Override 55 56 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 } 56 62 final JPanel p = new JPanel(new GridBagLayout()); 57 export.setSelected(true);58 63 final JLabel lbl = new JLabel(layer.getName(), layer.getIcon(), SwingConstants.LEADING); 59 64 lbl.setToolTipText(layer.getToolTipText()); 60 65 lbl.setLabelFor(export); … … 66 71 67 72 @Override 68 73 public boolean requiresZip() { 69 return true;74 return canExport; 70 75 } 71 76 72 77 @Override 73 78 public Element export(ExportSupport support) throws IOException { 79 if (!canExport) return null; 80 74 81 Element layerEl = support.createElement("layer"); 75 82 layerEl.setAttribute("type", "markers"); 76 83 layerEl.setAttribute("version", "0.1"); -
src/org/openstreetmap/josm/io/session/MarkerSessionImporter.java
48 48 49 49 support.addPostLayersTask(importData.getPostLayerTask()); 50 50 51 importData.getGpxLayer().destroy(); 51 52 return importData.getMarkerLayer(); 52 53 } 53 54 } catch (XPathExpressionException e) { -
src/org/openstreetmap/josm/io/session/SessionReader.java
14 14 import java.net.URISyntaxException; 15 15 import java.nio.charset.StandardCharsets; 16 16 import java.nio.file.Files; 17 import java.util.AbstractMap.SimpleEntry; 17 18 import java.util.ArrayList; 18 19 import java.util.Collection; 19 20 import java.util.Collections; … … 250 251 private final String layerName; 251 252 private final int layerIndex; 252 253 private final List<LayerDependency> layerDependencies; 254 private Map<Integer, Entry<Layer, Element>> subLayers; 253 255 254 256 /** 255 257 * Path of the file inside the zip archive. … … 279 281 } 280 282 281 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 xxx 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 xxx 303 */ 304 public Map<Integer, Entry<Layer, Element>> getSubLayers() { 305 return subLayers; 306 } 307 308 /** 282 309 * Return an InputStream for a URI from a .jos/.joz file. 283 310 * 284 311 * The following forms are supported: … … 506 533 List<Integer> sorted = Utils.topologicalSort(deps); 507 534 final Map<Integer, Layer> layersMap = new TreeMap<>(Collections.reverseOrder()); 508 535 final Map<Integer, SessionLayerImporter> importers = new HashMap<>(); 509 final Map<Integer, String> names = new HashMap<>();510 536 511 537 progressMonitor.setTicksCount(sorted.size()); 512 538 LAYER: for (int idx: sorted) { … … 519 545 return; 520 546 } 521 547 String name = e.getAttribute("name"); 522 names.put(idx, name);523 548 if (!e.hasAttribute("type")) { 524 549 error(tr("missing mandatory attribute ''type'' for element ''layer''")); 525 550 return; … … 595 620 } 596 621 597 622 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 598 635 } 599 636 progressMonitor.worked(1); 600 637 } 601 638 639 602 640 layers = new ArrayList<>(); 603 641 for (Entry<Integer, Layer> entry : layersMap.entrySet()) { 604 642 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"))); 643 if (layer != null) { 644 layers.add(layer); 611 645 } 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 } 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); 619 665 } 620 layer.setName(names.get(entry.getKey()));621 layers.add(layer);622 666 } 623 667 } 624 668 -
src/org/openstreetmap/josm/io/session/SessionWriter.java
174 174 } 175 175 176 176 /** 177 * Get the index of the specified layer 178 * @param layer the layer 179 * @return the index of the specified layer 180 * @since xxx 181 */ 182 public int getLayerIndexOf(Layer layer) { 183 return layers.indexOf(layer) + 1; 184 } 185 186 /** 177 187 * Create a file inside the zip archive. 178 188 * 179 189 * @param zipPath the path inside the zip archive, e.g. "layers/03/data.xml" … … 234 244 SessionLayerExporter exporter = exporters.get(layer); 235 245 ExportSupport support = new ExportSupport(doc, index+1); 236 246 Element el = exporter.export(support); 247 if (el == null) continue; 237 248 el.setAttribute("index", Integer.toString(index+1)); 238 249 el.setAttribute("name", layer.getName()); 239 250 el.setAttribute("visible", Boolean.toString(layer.isVisible())); -
src/org/openstreetmap/josm/tools/ListenerList.java
143 143 * @return <code>true</code> if any are registered. 144 144 */ 145 145 public boolean hasListeners() { 146 return !listeners.isEmpty() ;146 return !listeners.isEmpty() || weakListeners.stream().map(l -> l.listener.get()).anyMatch(Objects::nonNull); 147 147 } 148 148 149 149 /** -
test/data/sessions/gpx_markers_combined.jos
1 <?xml version="1.0" encoding="utf-8"?> 2 <josm-session version="0.1"> 3 <viewport> 4 <center lat="0.0" lon="0.0"/> 5 <scale meter-per-pixel="10.000000"/> 6 </viewport> 7 <projection> 8 <projection-choice> 9 <id>core:mercator</id> 10 <parameters/> 11 </projection-choice> 12 <code>EPSG:3857</code> 13 </projection> 14 <layers> 15 <layer index="1" name="GPX layer name" type="tracks" version="0.1" visible="true"> 16 <file>layers/01/data.gpx</file> 17 <markerLayer index="2" name="Marker layer name" opacity="0.5" visible="true"/> 18 </layer> 19 </layers> 20 </josm-session> -
test/unit/org/openstreetmap/josm/actions/SessionSaveActionTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.actions; 3 4 import static org.junit.Assert.assertFalse; 5 import static org.junit.jupiter.api.Assertions.assertEquals; 6 import static org.junit.jupiter.api.Assertions.assertTrue; 7 8 import java.io.File; 9 import java.io.IOException; 10 import java.util.Arrays; 11 import java.util.Collections; 12 13 import org.junit.jupiter.api.Test; 14 import org.junit.jupiter.api.extension.RegisterExtension; 15 import org.openstreetmap.josm.TestUtils; 16 import org.openstreetmap.josm.gui.MainApplication; 17 import org.openstreetmap.josm.gui.layer.GpxLayer; 18 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 19 import org.openstreetmap.josm.io.session.SessionWriterTest; 20 import org.openstreetmap.josm.testutils.JOSMTestRules; 21 import org.openstreetmap.josm.testutils.mockers.JOptionPaneSimpleMocker; 22 23 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 24 25 /** 26 * Unit tests for class {@link SessionSaveAsAction}. 27 */ 28 class SessionSaveActionTest { 29 /** 30 * Setup test. 31 */ 32 @RegisterExtension 33 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD") 34 public JOSMTestRules test = new JOSMTestRules().main().projection(); 35 36 /** 37 * Unit test of {@link SessionSaveAction} 38 * @throws IOException Temp file could not be created 39 */ 40 @Test 41 void testSaveAction() throws IOException { 42 TestUtils.assumeWorkingJMockit(); 43 44 File jos = File.createTempFile("session", ".jos"); 45 File joz = new File(jos.getAbsolutePath().replaceFirst(".jos$", ".joz")); 46 assertTrue(jos.exists()); 47 assertFalse(joz.exists()); 48 49 String overrideStr = "javax.swing.JLabel[,0,0,0x0,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=8388608,maximumSize=,minimumSize=," 50 + "preferredSize=,defaultIcon=,disabledIcon=,horizontalAlignment=LEADING,horizontalTextPosition=TRAILING,iconTextGap=4," 51 + "labelFor=,text=<html>The following layer has been removed since the session was last saved:<ul><li>OSM layer name</ul>" 52 + "<br>You are about to overwrite the session file \"" + joz.getName() 53 + "\". Would you like to proceed?,verticalAlignment=CENTER,verticalTextPosition=CENTER]"; 54 55 SessionSaveAction saveAction = SessionSaveAction.getInstance(); 56 saveAction.setEnabled(true); 57 58 OsmDataLayer osm = SessionWriterTest.createOsmLayer(); 59 GpxLayer gpx = SessionWriterTest.createGpxLayer(); 60 61 JOptionPaneSimpleMocker mocker = new JOptionPaneSimpleMocker(Collections.singletonMap(overrideStr, 0)); 62 SessionSaveAction.setCurrentSession(jos, false, Arrays.asList(gpx, osm)); //gpx and OSM layer 63 MainApplication.getLayerManager().addLayer(gpx); //only gpx layer 64 saveAction.actionPerformed(null); //Complain that OSM layer was removed 65 assertEquals(1, mocker.getInvocationLog().size()); 66 assertFalse(jos.exists()); 67 assertTrue(joz.exists()); //converted jos to joz since the session includes files 68 69 mocker = new JOptionPaneSimpleMocker(Collections.singletonMap(overrideStr, 0)); 70 joz.delete(); 71 saveAction.actionPerformed(null); //Do not complain about removed layers 72 assertEquals(0, mocker.getInvocationLog().size()); 73 assertTrue(joz.exists()); 74 75 joz.delete(); 76 } 77 } -
test/unit/org/openstreetmap/josm/actions/SessionSaveAsActionTest.java
Property changes on: test\unit\org\openstreetmap\josm\actions\SessionSaveActionTest.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain
3 3 4 4 import static org.junit.jupiter.api.Assertions.assertFalse; 5 5 6 import org.junit.jupiter.api.extension.RegisterExtension;7 6 import org.junit.jupiter.api.Test; 7 import org.junit.jupiter.api.extension.RegisterExtension; 8 8 import org.openstreetmap.josm.testutils.JOSMTestRules; 9 9 10 10 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; … … 19 19 */ 20 20 @RegisterExtension 21 21 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD") 22 public JOSMTestRules test = new JOSMTestRules() ;22 public JOSMTestRules test = new JOSMTestRules().main(); 23 23 24 24 /** 25 25 * Unit test of {@link SessionSaveAsAction#actionPerformed} -
test/unit/org/openstreetmap/josm/io/session/SessionWriterTest.java
50 50 /** 51 51 * Unit tests for Session writing. 52 52 */ 53 class SessionWriterTest {53 public class SessionWriterTest { 54 54 55 55 protected static final class OsmHeadlessJosExporter extends OsmDataSessionExporter { 56 56 public OsmHeadlessJosExporter(OsmDataLayer layer) { … … 122 122 } 123 123 for (final Layer l : layers) { 124 124 SessionLayerExporter s = SessionWriter.getSessionLayerExporter(l); 125 s.getExportPanel(); 125 126 exporters.put(l, s); 126 127 if (s instanceof GpxTracksSessionExporter) { 127 128 ((GpxTracksSessionExporter) s).setMetaTime(Instant.parse("2021-10-16T18:27:12.351Z")); … … 153 154 } 154 155 } 155 156 156 private OsmDataLayer createOsmLayer() { 157 /** 158 * Returns OSM layer 159 * @return OSM layer 160 */ 161 public static OsmDataLayer createOsmLayer() { 157 162 OsmDataLayer layer = new OsmDataLayer(new DataSet(), "OSM layer name", null); 158 163 layer.setAssociatedFile(new File("data.osm")); 159 164 return layer; 160 165 } 161 166 162 private GpxLayer createGpxLayer() { 167 /**Returns GPX layer 168 * @return GPX layer 169 */ 170 public static GpxLayer createGpxLayer() { 163 171 GpxData data = new GpxData(); 164 172 WayPoint wp = new WayPoint(new LatLon(42.72665, -0.00747)); 165 173 wp.setInstant(Instant.parse("2021-01-01T10:15:30.00Z")); … … 170 178 return layer; 171 179 } 172 180 173 private MarkerLayer createMarkerLayer(GpxLayer gpx) { 181 /** 182 * Returns MarkerLayer 183 * @param gpx linked GPX layer 184 * @return MarkerLayer 185 */ 186 public static MarkerLayer createMarkerLayer(GpxLayer gpx) { 174 187 MarkerLayer layer = new MarkerLayer(gpx.data, "Marker layer name", gpx.getAssociatedFile(), gpx); 175 188 layer.setOpacity(0.5); 176 189 layer.setColor(new Color(0x12345678, true)); 190 gpx.setLinkedMarkerLayer(layer); 177 191 return layer; 178 192 } 179 193 180 private ImageryLayer createImageryLayer() { 194 /** 195 * Returns ImageryLayer 196 * @return ImageryLayer 197 */ 198 public static ImageryLayer createImageryLayer() { 181 199 TMSLayer layer = new TMSLayer(new ImageryInfo("the name", "http://www.url.com/")); 182 200 layer.getDisplaySettings().setOffsetBookmark( 183 201 new OffsetBookmark(ProjectionRegistry.getProjection().toCode(), layer.getInfo().getId(), layer.getInfo().getName(), "", 12, 34)); 184 202 return layer; 185 203 } 186 204 187 private NoteLayer createNoteLayer() { 205 /** 206 * Returns NoteLayer 207 * @return NoteLayer 208 */ 209 public static NoteLayer createNoteLayer() { 188 210 return new NoteLayer(Arrays.asList(new Note(LatLon.ZERO)), "layer name"); 189 211 } 190 212 … … 249 271 @Test 250 272 void testWriteGpxAndMarkerJoz() throws IOException { 251 273 GpxLayer gpx = createGpxLayer(); 252 Map<String, byte[]> bytes = testWrite(Arrays.asList(gpx, createMarkerLayer(gpx)), true); 274 MarkerLayer markers = createMarkerLayer(gpx); 275 Map<String, byte[]> bytes = testWrite(Arrays.asList(gpx, markers), true); 253 276 254 Path path = Paths.get(TestUtils.getTestDataRoot() + "/sessions/gpx_markers .jos");277 Path path = Paths.get(TestUtils.getTestDataRoot() + "/sessions/gpx_markers_combined.jos"); 255 278 String expected = new String(Files.readAllBytes(path), StandardCharsets.UTF_8).replace("\r", ""); 256 279 String actual = new String(bytes.get("session.jos"), StandardCharsets.UTF_8).replace("\r", ""); 257 280 assertEquals(expected, actual); … … 261 284 actual = new String(bytes.get("layers/01/data.gpx"), StandardCharsets.UTF_8).replace("\r", ""); 262 285 assertEquals(expected, actual); 263 286 287 //Test writing when the marker layer has no corresponding GPX layer: 288 gpx.setLinkedMarkerLayer(null); 289 markers.fromLayer = null; 290 markers.data.transferLayerPrefs(gpx.data.getLayerPrefs()); 291 bytes = testWrite(Arrays.asList(gpx, markers), true); 292 293 path = Paths.get(TestUtils.getTestDataRoot() + "/sessions/gpx_markers.jos"); 294 expected = new String(Files.readAllBytes(path), StandardCharsets.UTF_8).replace("\r", ""); 295 actual = new String(bytes.get("session.jos"), StandardCharsets.UTF_8).replace("\r", ""); 296 assertEquals(expected, actual); 297 298 path = Paths.get(TestUtils.getTestDataRoot() + "/sessions/data_export.gpx"); 299 expected = new String(Files.readAllBytes(path), StandardCharsets.UTF_8).replace("\r", ""); 300 actual = new String(bytes.get("layers/01/data.gpx"), StandardCharsets.UTF_8).replace("\r", ""); 301 assertEquals(expected, actual); 302 264 303 path = Paths.get(TestUtils.getTestDataRoot() + "/sessions/markers.gpx"); 265 304 expected = new String(Files.readAllBytes(path), StandardCharsets.UTF_8).replace("\r", ""); 266 305 actual = new String(bytes.get("layers/02/data.gpx"), StandardCharsets.UTF_8).replace("\r", ""); 267 306 assertEquals(expected, actual); 307 268 308 } 269 309 270 310 /**