| [18466] | 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; | 
|---|
| [18472] | 16 | import java.nio.file.Files; | 
|---|
| [18466] | 17 | import java.util.ArrayList; | 
|---|
|  | 18 | import java.util.Arrays; | 
|---|
|  | 19 | import java.util.Collection; | 
|---|
| [18833] | 20 | import java.util.EnumSet; | 
|---|
| [18466] | 21 | import java.util.HashMap; | 
|---|
|  | 22 | import java.util.HashSet; | 
|---|
|  | 23 | import java.util.List; | 
|---|
|  | 24 | import java.util.Map; | 
|---|
|  | 25 | import java.util.Objects; | 
|---|
|  | 26 | import java.util.Set; | 
|---|
|  | 27 | import java.util.stream.Collectors; | 
|---|
|  | 28 | import java.util.stream.Stream; | 
|---|
|  | 29 |  | 
|---|
|  | 30 | import javax.swing.BorderFactory; | 
|---|
|  | 31 | import javax.swing.JCheckBox; | 
|---|
|  | 32 | import javax.swing.JFileChooser; | 
|---|
|  | 33 | import javax.swing.JLabel; | 
|---|
|  | 34 | import javax.swing.JOptionPane; | 
|---|
|  | 35 | import javax.swing.JPanel; | 
|---|
|  | 36 | import javax.swing.JScrollPane; | 
|---|
|  | 37 | import javax.swing.JTabbedPane; | 
|---|
|  | 38 | import javax.swing.SwingConstants; | 
|---|
|  | 39 | import javax.swing.border.EtchedBorder; | 
|---|
|  | 40 | import javax.swing.filechooser.FileFilter; | 
|---|
|  | 41 |  | 
|---|
|  | 42 | import org.openstreetmap.josm.data.PreferencesUtils; | 
|---|
|  | 43 | import org.openstreetmap.josm.data.preferences.BooleanProperty; | 
|---|
|  | 44 | import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil; | 
|---|
|  | 45 | import org.openstreetmap.josm.gui.ExtendedDialog; | 
|---|
|  | 46 | import org.openstreetmap.josm.gui.HelpAwareOptionPane; | 
|---|
|  | 47 | import org.openstreetmap.josm.gui.MainApplication; | 
|---|
|  | 48 | import org.openstreetmap.josm.gui.MapFrame; | 
|---|
|  | 49 | import org.openstreetmap.josm.gui.MapFrameListener; | 
|---|
|  | 50 | import org.openstreetmap.josm.gui.Notification; | 
|---|
|  | 51 | import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer; | 
|---|
|  | 52 | import org.openstreetmap.josm.gui.layer.Layer; | 
|---|
|  | 53 | import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent; | 
|---|
|  | 54 | import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener; | 
|---|
|  | 55 | import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent; | 
|---|
|  | 56 | import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent; | 
|---|
| [18472] | 57 | import org.openstreetmap.josm.gui.util.GuiHelper; | 
|---|
| [18466] | 58 | import org.openstreetmap.josm.gui.util.WindowGeometry; | 
|---|
|  | 59 | import org.openstreetmap.josm.gui.widgets.AbstractFileChooser; | 
|---|
| [18833] | 60 | import org.openstreetmap.josm.io.session.PluginSessionExporter; | 
|---|
| [18466] | 61 | import org.openstreetmap.josm.io.session.SessionLayerExporter; | 
|---|
|  | 62 | import org.openstreetmap.josm.io.session.SessionWriter; | 
|---|
| [18833] | 63 | import org.openstreetmap.josm.plugins.PluginHandler; | 
|---|
| [18466] | 64 | import org.openstreetmap.josm.spi.preferences.Config; | 
|---|
|  | 65 | import org.openstreetmap.josm.tools.GBC; | 
|---|
|  | 66 | import org.openstreetmap.josm.tools.JosmRuntimeException; | 
|---|
|  | 67 | import org.openstreetmap.josm.tools.Logging; | 
|---|
|  | 68 | import org.openstreetmap.josm.tools.MultiMap; | 
|---|
|  | 69 | import org.openstreetmap.josm.tools.Shortcut; | 
|---|
|  | 70 | import org.openstreetmap.josm.tools.UserCancelException; | 
|---|
|  | 71 | import org.openstreetmap.josm.tools.Utils; | 
|---|
|  | 72 |  | 
|---|
|  | 73 | /** | 
|---|
|  | 74 | * Saves a JOSM session | 
|---|
|  | 75 | * @since 18466 | 
|---|
|  | 76 | */ | 
|---|
|  | 77 | public class SessionSaveAction extends DiskAccessAction implements MapFrameListener, LayerChangeListener { | 
|---|
|  | 78 |  | 
|---|
|  | 79 | private transient List<Layer> layers; | 
|---|
|  | 80 | private transient Map<Layer, SessionLayerExporter> exporters; | 
|---|
|  | 81 | private transient MultiMap<Layer, Layer> dependencies; | 
|---|
|  | 82 |  | 
|---|
|  | 83 | private static final BooleanProperty SAVE_LOCAL_FILES_PROPERTY = new BooleanProperty("session.savelocal", true); | 
|---|
| [18833] | 84 | private static final BooleanProperty SAVE_PLUGIN_INFORMATION_PROPERTY = new BooleanProperty("session.saveplugins", false); | 
|---|
| [18466] | 85 | private static final String TOOLTIP_DEFAULT = tr("Save the current session."); | 
|---|
|  | 86 |  | 
|---|
| [18485] | 87 | protected transient FileFilter joz = new ExtensionFileFilter("joz", "joz", tr("Session file (archive) (*.joz)")); | 
|---|
|  | 88 | protected transient FileFilter jos = new ExtensionFileFilter("jos", "jos", tr("Session file (*.jos)")); | 
|---|
| [18466] | 89 |  | 
|---|
|  | 90 | private File removeFileOnSuccess; | 
|---|
|  | 91 |  | 
|---|
|  | 92 | private static String tooltip = TOOLTIP_DEFAULT; | 
|---|
| [18485] | 93 | static File sessionFile; | 
|---|
|  | 94 | static boolean isZipSessionFile; | 
|---|
| [18833] | 95 | private static boolean pluginData; | 
|---|
| [18485] | 96 | static List<WeakReference<Layer>> layersInSessionFile; | 
|---|
| [18466] | 97 |  | 
|---|
|  | 98 | private static final SessionSaveAction instance = new SessionSaveAction(); | 
|---|
|  | 99 |  | 
|---|
|  | 100 | /** | 
|---|
|  | 101 | * Returns the instance | 
|---|
|  | 102 | * @return the instance | 
|---|
|  | 103 | */ | 
|---|
| [18485] | 104 | public static SessionSaveAction getInstance() { | 
|---|
| [18466] | 105 | return instance; | 
|---|
|  | 106 | } | 
|---|
|  | 107 |  | 
|---|
|  | 108 | /** | 
|---|
|  | 109 | * Constructs a new {@code SessionSaveAction}. | 
|---|
|  | 110 | */ | 
|---|
|  | 111 | public SessionSaveAction() { | 
|---|
|  | 112 | this(true, false); | 
|---|
|  | 113 | updateEnabledState(); | 
|---|
|  | 114 | } | 
|---|
|  | 115 |  | 
|---|
|  | 116 | /** | 
|---|
|  | 117 | * Constructs a new {@code SessionSaveAction}. | 
|---|
|  | 118 | * @param toolbar Register this action for the toolbar preferences? | 
|---|
|  | 119 | * @param installAdapters False, if you don't want to install layer changed and selection changed adapters | 
|---|
|  | 120 | */ | 
|---|
|  | 121 | protected SessionSaveAction(boolean toolbar, boolean installAdapters) { | 
|---|
|  | 122 | this(tr("Save Session"), "session", TOOLTIP_DEFAULT, | 
|---|
|  | 123 | Shortcut.registerShortcut("system:savesession", tr("File: {0}", tr("Save Session...")), KeyEvent.VK_S, Shortcut.ALT_CTRL), | 
|---|
|  | 124 | toolbar, "save-session", installAdapters); | 
|---|
|  | 125 | setHelpId(ht("/Action/SessionSave")); | 
|---|
|  | 126 | } | 
|---|
|  | 127 |  | 
|---|
|  | 128 | protected SessionSaveAction(String name, String iconName, String tooltip, | 
|---|
|  | 129 | Shortcut shortcut, boolean register, String toolbarId, boolean installAdapters) { | 
|---|
|  | 130 |  | 
|---|
|  | 131 | super(name, iconName, tooltip, shortcut, register, toolbarId, installAdapters); | 
|---|
|  | 132 | addListeners(); | 
|---|
|  | 133 | } | 
|---|
|  | 134 |  | 
|---|
|  | 135 | @Override | 
|---|
|  | 136 | public void actionPerformed(ActionEvent e) { | 
|---|
|  | 137 | try { | 
|---|
|  | 138 | saveSession(false, false); | 
|---|
| [18485] | 139 | } catch (UserCancelException exception) { | 
|---|
|  | 140 | Logging.trace(exception); | 
|---|
| [18466] | 141 | } | 
|---|
|  | 142 | } | 
|---|
|  | 143 |  | 
|---|
|  | 144 | @Override | 
|---|
|  | 145 | public void destroy() { | 
|---|
|  | 146 | removeListeners(); | 
|---|
|  | 147 | super.destroy(); | 
|---|
|  | 148 | } | 
|---|
|  | 149 |  | 
|---|
|  | 150 | /** | 
|---|
|  | 151 | * Attempts to save the session. | 
|---|
|  | 152 | * @param saveAs true shows the dialog | 
|---|
|  | 153 | * @param forceSaveAll saves all layers | 
|---|
|  | 154 | * @return if the session and all layers were successfully saved | 
|---|
|  | 155 | * @throws UserCancelException when the user has cancelled the save process | 
|---|
|  | 156 | */ | 
|---|
|  | 157 | public boolean saveSession(boolean saveAs, boolean forceSaveAll) throws UserCancelException { | 
|---|
| [18942] | 158 | try { | 
|---|
|  | 159 | return saveSessionImpl(saveAs, forceSaveAll); | 
|---|
|  | 160 | } finally { | 
|---|
|  | 161 | cleanup(); | 
|---|
|  | 162 | } | 
|---|
|  | 163 | } | 
|---|
|  | 164 |  | 
|---|
|  | 165 | private boolean saveSessionImpl(boolean saveAs, boolean forceSaveAll) throws UserCancelException { | 
|---|
| [18466] | 166 | if (!isEnabled()) { | 
|---|
|  | 167 | return false; | 
|---|
|  | 168 | } | 
|---|
|  | 169 |  | 
|---|
|  | 170 | removeFileOnSuccess = null; | 
|---|
|  | 171 |  | 
|---|
|  | 172 | SessionSaveAsDialog dlg = new SessionSaveAsDialog(); | 
|---|
|  | 173 | if (saveAs) { | 
|---|
|  | 174 | dlg.showDialog(); | 
|---|
|  | 175 | if (dlg.getValue() != 1) { | 
|---|
|  | 176 | throw new UserCancelException(); | 
|---|
|  | 177 | } | 
|---|
|  | 178 | } | 
|---|
|  | 179 |  | 
|---|
|  | 180 | // TODO: resolve dependencies for layers excluded by the user | 
|---|
|  | 181 | List<Layer> layersOut = layers.stream() | 
|---|
|  | 182 | .filter(layer -> exporters.get(layer) != null && exporters.get(layer).shallExport()) | 
|---|
|  | 183 | .collect(Collectors.toList()); | 
|---|
|  | 184 |  | 
|---|
|  | 185 | boolean zipRequired = layersOut.stream().map(l -> exporters.get(l)) | 
|---|
| [18833] | 186 | .anyMatch(ex -> ex != null && ex.requiresZip()) || pluginsWantToSave(); | 
|---|
| [18466] | 187 |  | 
|---|
|  | 188 | saveAs = !doGetFile(saveAs, zipRequired); | 
|---|
|  | 189 |  | 
|---|
|  | 190 | String fn = sessionFile.getName(); | 
|---|
|  | 191 |  | 
|---|
|  | 192 | if (!saveAs && layersInSessionFile != null) { | 
|---|
|  | 193 | List<String> missingLayers = layersInSessionFile.stream() | 
|---|
|  | 194 | .map(WeakReference::get) | 
|---|
|  | 195 | .filter(Objects::nonNull) | 
|---|
|  | 196 | .filter(l -> !layersOut.contains(l)) | 
|---|
|  | 197 | .map(Layer::getName) | 
|---|
|  | 198 | .collect(Collectors.toList()); | 
|---|
|  | 199 |  | 
|---|
|  | 200 | if (!missingLayers.isEmpty() && | 
|---|
|  | 201 | !ConditionalOptionPaneUtil.showConfirmationDialog( | 
|---|
|  | 202 | "savesession_layerremoved", | 
|---|
|  | 203 | null, | 
|---|
|  | 204 | new JLabel("<html>" | 
|---|
|  | 205 | + trn("The following layer has been removed since the session was last saved:", | 
|---|
|  | 206 | "The following layers have been removed since the session was last saved:", missingLayers.size()) | 
|---|
|  | 207 | + "<ul><li>" | 
|---|
|  | 208 | + String.join("<li>", missingLayers) | 
|---|
|  | 209 | + "</ul><br>" | 
|---|
|  | 210 | + tr("You are about to overwrite the session file \"{0}\". Would you like to proceed?", fn)), | 
|---|
|  | 211 | tr("Layers removed"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, | 
|---|
|  | 212 | JOptionPane.OK_OPTION)) { | 
|---|
|  | 213 | throw new UserCancelException(); | 
|---|
|  | 214 | } | 
|---|
|  | 215 | } | 
|---|
|  | 216 | setCurrentLayers(layersOut); | 
|---|
|  | 217 |  | 
|---|
| [18485] | 218 | updateSessionFile(fn); | 
|---|
| [18466] | 219 |  | 
|---|
|  | 220 | Stream<Layer> layersToSaveStream = layersOut.stream() | 
|---|
|  | 221 | .filter(layer -> layer.isSavable() | 
|---|
|  | 222 | && layer instanceof AbstractModifiableLayer | 
|---|
|  | 223 | && ((AbstractModifiableLayer) layer).requiresSaveToFile() | 
|---|
|  | 224 | && exporters.get(layer) != null | 
|---|
|  | 225 | && !exporters.get(layer).requiresZip()); | 
|---|
|  | 226 |  | 
|---|
|  | 227 | boolean success = true; | 
|---|
| [18485] | 228 | if (forceSaveAll || Boolean.TRUE.equals(SAVE_LOCAL_FILES_PROPERTY.get())) { | 
|---|
| [18466] | 229 | // individual files must be saved before the session file as the location may change | 
|---|
|  | 230 | if (layersToSaveStream | 
|---|
|  | 231 | .map(layer -> SaveAction.getInstance().doSave(layer, true)) | 
|---|
|  | 232 | .collect(Collectors.toList()) // force evaluation of all elements | 
|---|
|  | 233 | .contains(false)) { | 
|---|
|  | 234 |  | 
|---|
|  | 235 | new Notification(tr("Not all local files referenced by the session file could be saved." | 
|---|
|  | 236 | + "<br>Make sure you save them before closing JOSM.")) | 
|---|
|  | 237 | .setIcon(JOptionPane.WARNING_MESSAGE) | 
|---|
|  | 238 | .setDuration(Notification.TIME_LONG) | 
|---|
|  | 239 | .show(); | 
|---|
|  | 240 | success = false; | 
|---|
|  | 241 | } | 
|---|
|  | 242 | } else if (layersToSaveStream.anyMatch(l -> true)) { | 
|---|
|  | 243 | new Notification(tr("Not all local files referenced by the session file are saved yet." | 
|---|
|  | 244 | + "<br>Make sure you save them before closing JOSM.")) | 
|---|
|  | 245 | .setIcon(JOptionPane.INFORMATION_MESSAGE) | 
|---|
|  | 246 | .setDuration(Notification.TIME_LONG) | 
|---|
|  | 247 | .show(); | 
|---|
|  | 248 | } | 
|---|
|  | 249 |  | 
|---|
|  | 250 | int active = -1; | 
|---|
|  | 251 | Layer activeLayer = getLayerManager().getActiveLayer(); | 
|---|
|  | 252 | if (activeLayer != null) { | 
|---|
|  | 253 | active = layersOut.indexOf(activeLayer); | 
|---|
|  | 254 | } | 
|---|
|  | 255 |  | 
|---|
| [18833] | 256 | final EnumSet<SessionWriter.SessionWriterFlags> flags = EnumSet.noneOf(SessionWriter.SessionWriterFlags.class); | 
|---|
|  | 257 | if (pluginData || (Boolean.TRUE.equals(SAVE_PLUGIN_INFORMATION_PROPERTY.get()) && pluginsWantToSave())) { | 
|---|
|  | 258 | flags.add(SessionWriter.SessionWriterFlags.SAVE_PLUGIN_INFORMATION); | 
|---|
|  | 259 | } | 
|---|
|  | 260 | if (isZipSessionFile) { | 
|---|
|  | 261 | flags.add(SessionWriter.SessionWriterFlags.IS_ZIP); | 
|---|
|  | 262 | } | 
|---|
|  | 263 | SessionWriter sw = new SessionWriter(layersOut, active, exporters, dependencies, flags.toArray(new SessionWriter.SessionWriterFlags[0])); | 
|---|
| [18466] | 264 | try { | 
|---|
|  | 265 | Notification savingNotification = showSavingNotification(sessionFile.getName()); | 
|---|
|  | 266 | sw.write(sessionFile); | 
|---|
|  | 267 | SaveActionBase.addToFileOpenHistory(sessionFile); | 
|---|
|  | 268 | if (removeFileOnSuccess != null) { | 
|---|
|  | 269 | PreferencesUtils.removeFromList(Config.getPref(), "file-open.history", removeFileOnSuccess.getCanonicalPath()); | 
|---|
| [18472] | 270 | Files.deleteIfExists(removeFileOnSuccess.toPath()); | 
|---|
| [18466] | 271 | removeFileOnSuccess = null; | 
|---|
|  | 272 | } | 
|---|
|  | 273 | showSavedNotification(savingNotification, sessionFile.getName()); | 
|---|
| [18472] | 274 | } catch (SecurityException ex) { | 
|---|
|  | 275 | Logging.error(ex); | 
|---|
|  | 276 | if (removeFileOnSuccess != null) { | 
|---|
|  | 277 | final String path = removeFileOnSuccess.getPath(); | 
|---|
|  | 278 | GuiHelper.runInEDT(() -> { | 
|---|
| [18481] | 279 | Notification notification = new Notification(tr("Could not delete file: {0}<br>{1}", path, ex.getMessage())); | 
|---|
| [18472] | 280 | notification.setIcon(JOptionPane.WARNING_MESSAGE); | 
|---|
|  | 281 | notification.show(); | 
|---|
|  | 282 | }); | 
|---|
|  | 283 | } else { | 
|---|
|  | 284 | // We should never hit this, unless something changes in the try block. | 
|---|
|  | 285 | throw new JosmRuntimeException(ex); | 
|---|
|  | 286 | } | 
|---|
| [18466] | 287 | } catch (IOException ex) { | 
|---|
|  | 288 | Logging.error(ex); | 
|---|
|  | 289 | HelpAwareOptionPane.showMessageDialogInEDT( | 
|---|
|  | 290 | MainApplication.getMainFrame(), | 
|---|
|  | 291 | tr("<html>Could not save session file ''{0}''.<br>Error is:<br>{1}</html>", | 
|---|
|  | 292 | sessionFile.getName(), Utils.escapeReservedCharactersHTML(ex.getMessage())), | 
|---|
|  | 293 | tr("IO Error"), | 
|---|
|  | 294 | JOptionPane.ERROR_MESSAGE, | 
|---|
|  | 295 | null | 
|---|
|  | 296 | ); | 
|---|
|  | 297 | success = false; | 
|---|
|  | 298 | } | 
|---|
|  | 299 | return success; | 
|---|
|  | 300 | } | 
|---|
|  | 301 |  | 
|---|
|  | 302 | /** | 
|---|
|  | 303 | * Sets the current session file. Asks the user if necessary | 
|---|
| [18472] | 304 | * @param saveAs always ask the user | 
|---|
| [18466] | 305 | * @param zipRequired zip | 
|---|
|  | 306 | * @return if the user was asked | 
|---|
|  | 307 | * @throws UserCancelException when the user has cancelled the save process | 
|---|
|  | 308 | */ | 
|---|
|  | 309 | protected boolean doGetFile(boolean saveAs, boolean zipRequired) throws UserCancelException { | 
|---|
|  | 310 | if (!saveAs && sessionFile != null) { | 
|---|
|  | 311 |  | 
|---|
|  | 312 | if (isZipSessionFile || !zipRequired) | 
|---|
|  | 313 | return true; | 
|---|
|  | 314 |  | 
|---|
|  | 315 | Logging.info("Converting *.jos to *.joz because a new layer has been added that requires zip format"); | 
|---|
|  | 316 | String oldPath = sessionFile.getAbsolutePath(); | 
|---|
|  | 317 | int i = oldPath.lastIndexOf('.'); | 
|---|
|  | 318 | File jozFile = new File(i < 0 ? oldPath : oldPath.substring(0, i) + ".joz"); | 
|---|
|  | 319 | if (!jozFile.exists()) { | 
|---|
|  | 320 | removeFileOnSuccess = sessionFile; | 
|---|
|  | 321 | setCurrentSession(jozFile, true); | 
|---|
|  | 322 | return true; | 
|---|
|  | 323 | } | 
|---|
|  | 324 | Logging.warn("Asking user to choose a new location for the *.joz file because it already exists"); | 
|---|
|  | 325 | } | 
|---|
|  | 326 |  | 
|---|
|  | 327 | doGetFileChooser(zipRequired); | 
|---|
|  | 328 | return false; | 
|---|
|  | 329 | } | 
|---|
|  | 330 |  | 
|---|
|  | 331 | protected void doGetFileChooser(boolean zipRequired) throws UserCancelException { | 
|---|
|  | 332 | AbstractFileChooser fc; | 
|---|
|  | 333 |  | 
|---|
|  | 334 | if (zipRequired) { | 
|---|
|  | 335 | fc = createAndOpenFileChooser(false, false, tr("Save Session"), joz, JFileChooser.FILES_ONLY, "lastDirectory"); | 
|---|
|  | 336 | } else { | 
|---|
|  | 337 | fc = createAndOpenFileChooser(false, false, tr("Save Session"), Arrays.asList(jos, joz), jos, | 
|---|
|  | 338 | JFileChooser.FILES_ONLY, "lastDirectory"); | 
|---|
|  | 339 | } | 
|---|
|  | 340 |  | 
|---|
|  | 341 | if (fc == null) { | 
|---|
|  | 342 | throw new UserCancelException(); | 
|---|
|  | 343 | } | 
|---|
|  | 344 |  | 
|---|
|  | 345 | File f = fc.getSelectedFile(); | 
|---|
|  | 346 | FileFilter ff = fc.getFileFilter(); | 
|---|
|  | 347 | boolean zip; | 
|---|
|  | 348 |  | 
|---|
|  | 349 | if (zipRequired || joz.equals(ff)) { | 
|---|
|  | 350 | zip = true; | 
|---|
|  | 351 | } else if (jos.equals(ff)) { | 
|---|
|  | 352 | zip = false; | 
|---|
|  | 353 | } else { | 
|---|
|  | 354 | zip = Utils.hasExtension(f.getName(), "joz"); | 
|---|
|  | 355 | } | 
|---|
|  | 356 | setCurrentSession(f, zip); | 
|---|
|  | 357 | } | 
|---|
|  | 358 |  | 
|---|
|  | 359 | /** | 
|---|
|  | 360 | * The "Save Session" dialog | 
|---|
|  | 361 | */ | 
|---|
|  | 362 | public class SessionSaveAsDialog extends ExtendedDialog { | 
|---|
|  | 363 |  | 
|---|
|  | 364 | /** | 
|---|
|  | 365 | * Constructs a new {@code SessionSaveAsDialog}. | 
|---|
|  | 366 | */ | 
|---|
|  | 367 | public SessionSaveAsDialog() { | 
|---|
|  | 368 | super(MainApplication.getMainFrame(), tr("Save Session"), tr("Save As"), tr("Cancel")); | 
|---|
|  | 369 | configureContextsensitiveHelp("Action/SessionSaveAs", true /* show help button */); | 
|---|
|  | 370 | initialize(); | 
|---|
|  | 371 | setButtonIcons("save_as", "cancel"); | 
|---|
|  | 372 | setDefaultButton(1); | 
|---|
|  | 373 | setRememberWindowGeometry(getClass().getName() + ".geometry", | 
|---|
|  | 374 | WindowGeometry.centerInWindow(MainApplication.getMainFrame(), new Dimension(450, 450))); | 
|---|
|  | 375 | setContent(build(), false); | 
|---|
|  | 376 | } | 
|---|
|  | 377 |  | 
|---|
|  | 378 | /** | 
|---|
| [18942] | 379 | * Initializes some action fields. | 
|---|
| [18466] | 380 | */ | 
|---|
| [18942] | 381 | private void initialize() { | 
|---|
| [18466] | 382 | layers = new ArrayList<>(getLayerManager().getLayers()); | 
|---|
|  | 383 | exporters = new HashMap<>(); | 
|---|
|  | 384 | dependencies = new MultiMap<>(); | 
|---|
|  | 385 |  | 
|---|
|  | 386 | Set<Layer> noExporter = new HashSet<>(); | 
|---|
|  | 387 |  | 
|---|
|  | 388 | for (Layer layer : layers) { | 
|---|
|  | 389 | SessionLayerExporter exporter = null; | 
|---|
|  | 390 | try { | 
|---|
|  | 391 | exporter = SessionWriter.getSessionLayerExporter(layer); | 
|---|
|  | 392 | } catch (IllegalArgumentException | JosmRuntimeException e) { | 
|---|
|  | 393 | Logging.error(e); | 
|---|
|  | 394 | } | 
|---|
|  | 395 | if (exporter != null) { | 
|---|
|  | 396 | exporters.put(layer, exporter); | 
|---|
|  | 397 | Collection<Layer> deps = exporter.getDependencies(); | 
|---|
|  | 398 | if (deps != null) { | 
|---|
|  | 399 | dependencies.putAll(layer, deps); | 
|---|
|  | 400 | } else { | 
|---|
|  | 401 | dependencies.putVoid(layer); | 
|---|
|  | 402 | } | 
|---|
|  | 403 | } else { | 
|---|
|  | 404 | noExporter.add(layer); | 
|---|
|  | 405 | exporters.put(layer, null); | 
|---|
|  | 406 | } | 
|---|
|  | 407 | } | 
|---|
|  | 408 |  | 
|---|
|  | 409 | int numNoExporter = 0; | 
|---|
| [18485] | 410 | while (numNoExporter != noExporter.size()) { | 
|---|
| [18466] | 411 | numNoExporter = noExporter.size(); | 
|---|
| [18485] | 412 | updateExporters(noExporter); | 
|---|
|  | 413 | } | 
|---|
|  | 414 | } | 
|---|
|  | 415 |  | 
|---|
|  | 416 | private void updateExporters(Collection<Layer> noExporter) { | 
|---|
|  | 417 | for (Layer layer : layers) { | 
|---|
|  | 418 | if (noExporter.contains(layer)) continue; | 
|---|
|  | 419 | for (Layer depLayer : dependencies.get(layer)) { | 
|---|
|  | 420 | if (noExporter.contains(depLayer)) { | 
|---|
|  | 421 | noExporter.add(layer); | 
|---|
|  | 422 | exporters.put(layer, null); | 
|---|
|  | 423 | return; | 
|---|
| [18466] | 424 | } | 
|---|
|  | 425 | } | 
|---|
|  | 426 | } | 
|---|
|  | 427 | } | 
|---|
|  | 428 |  | 
|---|
|  | 429 | protected final Component build() { | 
|---|
|  | 430 | JPanel op = new JPanel(new GridBagLayout()); | 
|---|
|  | 431 | JPanel ip = new JPanel(new GridBagLayout()); | 
|---|
|  | 432 | for (Layer layer : layers) { | 
|---|
|  | 433 | Component exportPanel; | 
|---|
|  | 434 | SessionLayerExporter exporter = exporters.get(layer); | 
|---|
|  | 435 | if (exporter == null) { | 
|---|
|  | 436 | if (!exporters.containsKey(layer)) throw new AssertionError(); | 
|---|
|  | 437 | exportPanel = getDisabledExportPanel(layer); | 
|---|
|  | 438 | } else { | 
|---|
|  | 439 | exportPanel = exporter.getExportPanel(); | 
|---|
|  | 440 | } | 
|---|
|  | 441 | if (exportPanel == null) continue; | 
|---|
|  | 442 | JPanel wrapper = new JPanel(new GridBagLayout()); | 
|---|
|  | 443 | wrapper.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED)); | 
|---|
|  | 444 | wrapper.add(exportPanel, GBC.std().fill(GBC.HORIZONTAL)); | 
|---|
|  | 445 | ip.add(wrapper, GBC.eol().fill(GBC.HORIZONTAL).insets(2, 2, 4, 2)); | 
|---|
|  | 446 | } | 
|---|
|  | 447 | ip.add(GBC.glue(0, 1), GBC.eol().fill(GBC.VERTICAL)); | 
|---|
|  | 448 | JScrollPane sp = new JScrollPane(ip); | 
|---|
|  | 449 | sp.setBorder(BorderFactory.createEmptyBorder()); | 
|---|
|  | 450 | JPanel p = new JPanel(new GridBagLayout()); | 
|---|
|  | 451 | p.add(sp, GBC.eol().fill()); | 
|---|
|  | 452 | final JTabbedPane tabs = new JTabbedPane(); | 
|---|
|  | 453 | tabs.addTab(tr("Layers"), p); | 
|---|
|  | 454 | op.add(tabs, GBC.eol().fill()); | 
|---|
|  | 455 | JCheckBox chkSaveLocal = new JCheckBox(tr("Save all local files to disk"), SAVE_LOCAL_FILES_PROPERTY.get()); | 
|---|
| [18485] | 456 | chkSaveLocal.addChangeListener(l -> SAVE_LOCAL_FILES_PROPERTY.put(chkSaveLocal.isSelected())); | 
|---|
| [18833] | 457 | op.add(chkSaveLocal, GBC.eol()); | 
|---|
|  | 458 | if (pluginsWantToSave()) { | 
|---|
|  | 459 | JCheckBox chkSavePlugins = new JCheckBox(tr("Save plugin information to disk"), SAVE_PLUGIN_INFORMATION_PROPERTY.get()); | 
|---|
|  | 460 | chkSavePlugins.addChangeListener(l -> SAVE_PLUGIN_INFORMATION_PROPERTY.put(chkSavePlugins.isSelected())); | 
|---|
|  | 461 | chkSavePlugins.setToolTipText(tr("Plugins may have additional information that can be saved")); | 
|---|
|  | 462 | op.add(chkSavePlugins, GBC.eol()); | 
|---|
|  | 463 | } | 
|---|
| [18466] | 464 | return op; | 
|---|
|  | 465 | } | 
|---|
|  | 466 |  | 
|---|
|  | 467 | protected final Component getDisabledExportPanel(Layer layer) { | 
|---|
|  | 468 | JPanel p = new JPanel(new GridBagLayout()); | 
|---|
|  | 469 | JCheckBox include = new JCheckBox(); | 
|---|
|  | 470 | include.setEnabled(false); | 
|---|
|  | 471 | JLabel lbl = new JLabel(layer.getName(), layer.getIcon(), SwingConstants.LEADING); | 
|---|
|  | 472 | lbl.setToolTipText(tr("No exporter for this layer")); | 
|---|
|  | 473 | lbl.setLabelFor(include); | 
|---|
|  | 474 | lbl.setEnabled(false); | 
|---|
|  | 475 | p.add(include, GBC.std()); | 
|---|
|  | 476 | p.add(lbl, GBC.std()); | 
|---|
|  | 477 | p.add(GBC.glue(1, 0), GBC.std().fill(GBC.HORIZONTAL)); | 
|---|
|  | 478 | return p; | 
|---|
|  | 479 | } | 
|---|
|  | 480 | } | 
|---|
|  | 481 |  | 
|---|
|  | 482 | protected void addListeners() { | 
|---|
|  | 483 | MainApplication.addMapFrameListener(this); | 
|---|
|  | 484 | MainApplication.getLayerManager().addLayerChangeListener(this); | 
|---|
|  | 485 | } | 
|---|
|  | 486 |  | 
|---|
|  | 487 | protected void removeListeners() { | 
|---|
|  | 488 | MainApplication.removeMapFrameListener(this); | 
|---|
|  | 489 | MainApplication.getLayerManager().removeLayerChangeListener(this); | 
|---|
|  | 490 | } | 
|---|
|  | 491 |  | 
|---|
|  | 492 | @Override | 
|---|
|  | 493 | protected void updateEnabledState() { | 
|---|
|  | 494 | setEnabled(MainApplication.isDisplayingMapView()); | 
|---|
|  | 495 | } | 
|---|
|  | 496 |  | 
|---|
|  | 497 | @Override | 
|---|
|  | 498 | public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) { | 
|---|
|  | 499 | updateEnabledState(); | 
|---|
|  | 500 | } | 
|---|
|  | 501 |  | 
|---|
|  | 502 | @Override | 
|---|
|  | 503 | public void layerAdded(LayerAddEvent e) { | 
|---|
|  | 504 | // not used | 
|---|
|  | 505 | } | 
|---|
|  | 506 |  | 
|---|
|  | 507 | @Override | 
|---|
|  | 508 | public void layerRemoving(LayerRemoveEvent e) { | 
|---|
|  | 509 | if (e.isLastLayer()) { | 
|---|
|  | 510 | setCurrentSession(null, false); | 
|---|
|  | 511 | } | 
|---|
|  | 512 | } | 
|---|
|  | 513 |  | 
|---|
|  | 514 | @Override | 
|---|
|  | 515 | public void layerOrderChanged(LayerOrderChangeEvent e) { | 
|---|
|  | 516 | // not used | 
|---|
|  | 517 | } | 
|---|
|  | 518 |  | 
|---|
|  | 519 | /** | 
|---|
| [18485] | 520 | * Update the session file | 
|---|
|  | 521 | * @param fileName The filename to use. If there are no periods in the file, we update the extension. | 
|---|
|  | 522 | * @throws UserCancelException If the user does not want to overwrite a previously existing file. | 
|---|
|  | 523 | */ | 
|---|
|  | 524 | private static void updateSessionFile(String fileName) throws UserCancelException { | 
|---|
|  | 525 | if (fileName.indexOf('.') == -1) { | 
|---|
|  | 526 | sessionFile = new File(sessionFile.getPath() + (isZipSessionFile ? ".joz" : ".jos")); | 
|---|
|  | 527 | if (!SaveActionBase.confirmOverwrite(sessionFile)) { | 
|---|
|  | 528 | throw new UserCancelException(); | 
|---|
|  | 529 | } | 
|---|
|  | 530 | } | 
|---|
|  | 531 | } | 
|---|
|  | 532 |  | 
|---|
|  | 533 | /** | 
|---|
| [18466] | 534 | * Sets the current session file and the layers included in that file | 
|---|
|  | 535 | * @param file file | 
|---|
|  | 536 | * @param zip if it is a zip session file | 
|---|
|  | 537 | * @param layers layers that are currently represented in the session file | 
|---|
| [18833] | 538 | * @deprecated since 18833, use {@link #setCurrentSession(File, List, SessionWriter.SessionWriterFlags...)} instead | 
|---|
| [18466] | 539 | */ | 
|---|
| [18833] | 540 | @Deprecated | 
|---|
| [18466] | 541 | public static void setCurrentSession(File file, boolean zip, List<Layer> layers) { | 
|---|
| [18833] | 542 | if (zip) { | 
|---|
|  | 543 | setCurrentSession(file, layers, SessionWriter.SessionWriterFlags.IS_ZIP); | 
|---|
|  | 544 | } else { | 
|---|
|  | 545 | setCurrentSession(file, layers); | 
|---|
|  | 546 | } | 
|---|
|  | 547 | } | 
|---|
|  | 548 |  | 
|---|
|  | 549 | /** | 
|---|
|  | 550 | * Sets the current session file and the layers included in that file | 
|---|
|  | 551 | * @param file file | 
|---|
|  | 552 | * @param layers layers that are currently represented in the session file | 
|---|
|  | 553 | * @param flags The flags for the current session | 
|---|
|  | 554 | * @since 18833 | 
|---|
|  | 555 | */ | 
|---|
|  | 556 | public static void setCurrentSession(File file, List<Layer> layers, SessionWriter.SessionWriterFlags... flags) { | 
|---|
|  | 557 | final EnumSet<SessionWriter.SessionWriterFlags> flagSet = EnumSet.noneOf(SessionWriter.SessionWriterFlags.class); | 
|---|
|  | 558 | flagSet.addAll(Arrays.asList(flags)); | 
|---|
|  | 559 | setCurrentSession(file, layers, flagSet); | 
|---|
|  | 560 | } | 
|---|
|  | 561 |  | 
|---|
|  | 562 | /** | 
|---|
|  | 563 | * Sets the current session file and the layers included in that file | 
|---|
|  | 564 | * @param file file | 
|---|
|  | 565 | * @param layers layers that are currently represented in the session file | 
|---|
|  | 566 | * @param flags The flags for the current session | 
|---|
|  | 567 | * @since 18833 | 
|---|
|  | 568 | */ | 
|---|
|  | 569 | public static void setCurrentSession(File file, List<Layer> layers, Set<SessionWriter.SessionWriterFlags> flags) { | 
|---|
| [18466] | 570 | setCurrentLayers(layers); | 
|---|
| [18833] | 571 | setCurrentSession(file, flags.contains(SessionWriter.SessionWriterFlags.IS_ZIP)); | 
|---|
|  | 572 | pluginData = flags.contains(SessionWriter.SessionWriterFlags.SAVE_PLUGIN_INFORMATION); | 
|---|
| [18466] | 573 | } | 
|---|
|  | 574 |  | 
|---|
|  | 575 | /** | 
|---|
|  | 576 | * Sets the current session file | 
|---|
|  | 577 | * @param file file | 
|---|
|  | 578 | * @param zip if it is a zip session file | 
|---|
|  | 579 | */ | 
|---|
|  | 580 | public static void setCurrentSession(File file, boolean zip) { | 
|---|
|  | 581 | sessionFile = file; | 
|---|
|  | 582 | isZipSessionFile = zip; | 
|---|
|  | 583 | if (file == null) { | 
|---|
|  | 584 | tooltip = TOOLTIP_DEFAULT; | 
|---|
|  | 585 | } else { | 
|---|
|  | 586 | tooltip = tr("Save the current session file \"{0}\".", file.getName()); | 
|---|
|  | 587 | } | 
|---|
|  | 588 | getInstance().setTooltip(tooltip); | 
|---|
|  | 589 | } | 
|---|
|  | 590 |  | 
|---|
|  | 591 | /** | 
|---|
|  | 592 | * Sets the layers that are currently represented in the session file | 
|---|
|  | 593 | * @param layers layers | 
|---|
|  | 594 | */ | 
|---|
|  | 595 | public static void setCurrentLayers(List<Layer> layers) { | 
|---|
|  | 596 | layersInSessionFile = layers.stream() | 
|---|
| [18485] | 597 | .filter(AbstractModifiableLayer.class::isInstance) | 
|---|
| [18466] | 598 | .map(WeakReference::new) | 
|---|
|  | 599 | .collect(Collectors.toList()); | 
|---|
|  | 600 | } | 
|---|
|  | 601 |  | 
|---|
|  | 602 | /** | 
|---|
|  | 603 | * Returns the tooltip for the component | 
|---|
|  | 604 | * @return the tooltip for the component | 
|---|
|  | 605 | */ | 
|---|
|  | 606 | public static String getTooltip() { | 
|---|
|  | 607 | return tooltip; | 
|---|
|  | 608 | } | 
|---|
|  | 609 |  | 
|---|
| [18833] | 610 | /** | 
|---|
|  | 611 | * Check to see if any plugins want to save their state | 
|---|
|  | 612 | * @return {@code true} if the plugin wants to save their state | 
|---|
|  | 613 | */ | 
|---|
|  | 614 | private static boolean pluginsWantToSave() { | 
|---|
|  | 615 | for (PluginSessionExporter exporter : PluginHandler.load(PluginSessionExporter.class)) { | 
|---|
|  | 616 | if (exporter.requiresSaving()) { | 
|---|
|  | 617 | return true; | 
|---|
|  | 618 | } | 
|---|
|  | 619 | } | 
|---|
|  | 620 | return false; | 
|---|
|  | 621 | } | 
|---|
|  | 622 |  | 
|---|
| [18942] | 623 | protected void cleanup() { | 
|---|
|  | 624 | layers = null; | 
|---|
|  | 625 | exporters = null; | 
|---|
|  | 626 | dependencies = null; | 
|---|
|  | 627 | } | 
|---|
|  | 628 |  | 
|---|
| [18466] | 629 | } | 
|---|