Ticket #21923: 21923.patch
File 21923.patch, 49.1 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/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/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/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/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}