[4685] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
| 2 | package org.openstreetmap.josm.actions;
|
---|
| 3 |
|
---|
[5438] | 4 | import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
|
---|
[4685] | 5 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
| 6 |
|
---|
| 7 | import java.awt.Component;
|
---|
| 8 | import java.awt.Dimension;
|
---|
| 9 | import java.awt.GridBagLayout;
|
---|
| 10 | import java.awt.event.ActionEvent;
|
---|
| 11 | import java.io.File;
|
---|
| 12 | import java.io.IOException;
|
---|
| 13 | import java.util.ArrayList;
|
---|
[5438] | 14 | import java.util.Arrays;
|
---|
[4685] | 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;
|
---|
[16438] | 21 | import java.util.stream.Collectors;
|
---|
[4685] | 22 |
|
---|
| 23 | import javax.swing.BorderFactory;
|
---|
| 24 | import javax.swing.JCheckBox;
|
---|
| 25 | import javax.swing.JFileChooser;
|
---|
| 26 | import javax.swing.JLabel;
|
---|
| 27 | import javax.swing.JOptionPane;
|
---|
| 28 | import javax.swing.JPanel;
|
---|
| 29 | import javax.swing.JScrollPane;
|
---|
| 30 | import javax.swing.JTabbedPane;
|
---|
| 31 | import javax.swing.SwingConstants;
|
---|
| 32 | import javax.swing.border.EtchedBorder;
|
---|
| 33 | import javax.swing.filechooser.FileFilter;
|
---|
| 34 |
|
---|
| 35 | import org.openstreetmap.josm.gui.ExtendedDialog;
|
---|
| 36 | import org.openstreetmap.josm.gui.HelpAwareOptionPane;
|
---|
[12630] | 37 | import org.openstreetmap.josm.gui.MainApplication;
|
---|
[9629] | 38 | import org.openstreetmap.josm.gui.MapFrame;
|
---|
| 39 | import org.openstreetmap.josm.gui.MapFrameListener;
|
---|
[18135] | 40 | import org.openstreetmap.josm.gui.Notification;
|
---|
[4685] | 41 | import org.openstreetmap.josm.gui.layer.Layer;
|
---|
[12678] | 42 | import org.openstreetmap.josm.gui.util.WindowGeometry;
|
---|
[7578] | 43 | import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
|
---|
[4685] | 44 | import org.openstreetmap.josm.io.session.SessionLayerExporter;
|
---|
| 45 | import org.openstreetmap.josm.io.session.SessionWriter;
|
---|
| 46 | import org.openstreetmap.josm.tools.GBC;
|
---|
[18133] | 47 | import org.openstreetmap.josm.tools.JosmRuntimeException;
|
---|
[12620] | 48 | import org.openstreetmap.josm.tools.Logging;
|
---|
[4685] | 49 | import org.openstreetmap.josm.tools.MultiMap;
|
---|
[8919] | 50 | import org.openstreetmap.josm.tools.UserCancelException;
|
---|
[8404] | 51 | import org.openstreetmap.josm.tools.Utils;
|
---|
[4685] | 52 |
|
---|
[6245] | 53 | /**
|
---|
| 54 | * Saves a JOSM session
|
---|
| 55 | * @since 4685
|
---|
| 56 | */
|
---|
[9629] | 57 | public class SessionSaveAsAction extends DiskAccessAction implements MapFrameListener {
|
---|
[4685] | 58 |
|
---|
[8308] | 59 | private transient List<Layer> layers;
|
---|
| 60 | private transient Map<Layer, SessionLayerExporter> exporters;
|
---|
| 61 | private transient MultiMap<Layer, Layer> dependencies;
|
---|
[4685] | 62 |
|
---|
[6245] | 63 | /**
|
---|
| 64 | * Constructs a new {@code SessionSaveAsAction}.
|
---|
| 65 | */
|
---|
[4685] | 66 | public SessionSaveAsAction() {
|
---|
[16509] | 67 | this(true, false);
|
---|
| 68 | updateEnabledState();
|
---|
[9152] | 69 | }
|
---|
| 70 |
|
---|
| 71 | /**
|
---|
| 72 | * Constructs a new {@code SessionSaveAsAction}.
|
---|
| 73 | * @param toolbar Register this action for the toolbar preferences?
|
---|
| 74 | * @param installAdapters False, if you don't want to install layer changed and selection changed adapters
|
---|
| 75 | */
|
---|
| 76 | protected SessionSaveAsAction(boolean toolbar, boolean installAdapters) {
|
---|
[9173] | 77 | super(tr("Save Session As..."), "session", tr("Save the current session to a new file."),
|
---|
| 78 | null, toolbar, "save_as-session", installAdapters);
|
---|
[14397] | 79 | setHelpId(ht("/Action/SessionSaveAs"));
|
---|
[12639] | 80 | MainApplication.addMapFrameListener(this);
|
---|
[4685] | 81 | }
|
---|
| 82 |
|
---|
| 83 | @Override
|
---|
| 84 | public void actionPerformed(ActionEvent e) {
|
---|
[8913] | 85 | try {
|
---|
| 86 | saveSession();
|
---|
[8919] | 87 | } catch (UserCancelException ignore) {
|
---|
[12620] | 88 | Logging.trace(ignore);
|
---|
[8913] | 89 | }
|
---|
| 90 | }
|
---|
| 91 |
|
---|
[14470] | 92 | @Override
|
---|
| 93 | public void destroy() {
|
---|
| 94 | MainApplication.removeMapFrameListener(this);
|
---|
| 95 | super.destroy();
|
---|
| 96 | }
|
---|
| 97 |
|
---|
[8913] | 98 | /**
|
---|
| 99 | * Attempts to save the session.
|
---|
[8919] | 100 | * @throws UserCancelException when the user has cancelled the save process.
|
---|
[8913] | 101 | * @since 8913
|
---|
| 102 | */
|
---|
[8919] | 103 | public void saveSession() throws UserCancelException {
|
---|
[4685] | 104 | if (!isEnabled()) {
|
---|
| 105 | return;
|
---|
| 106 | }
|
---|
| 107 |
|
---|
| 108 | SessionSaveAsDialog dlg = new SessionSaveAsDialog();
|
---|
| 109 | dlg.showDialog();
|
---|
[8913] | 110 | if (dlg.getValue() != 1) {
|
---|
[8919] | 111 | throw new UserCancelException();
|
---|
[8913] | 112 | }
|
---|
[4685] | 113 |
|
---|
[16438] | 114 | boolean zipRequired = layers.stream().map(l -> exporters.get(l))
|
---|
| 115 | .anyMatch(ex -> ex != null && ex.requiresZip());
|
---|
[6069] | 116 |
|
---|
[4685] | 117 | FileFilter joz = new ExtensionFileFilter("joz", "joz", tr("Session file (archive) (*.joz)"));
|
---|
| 118 | FileFilter jos = new ExtensionFileFilter("jos", "jos", tr("Session file (*.jos)"));
|
---|
[5438] | 119 |
|
---|
[7578] | 120 | AbstractFileChooser fc;
|
---|
[5505] | 121 |
|
---|
[4685] | 122 | if (zipRequired) {
|
---|
[13761] | 123 | fc = createAndOpenFileChooser(false, false, tr("Save Session"), joz, JFileChooser.FILES_ONLY, "lastDirectory");
|
---|
[4685] | 124 | } else {
|
---|
[13761] | 125 | fc = createAndOpenFileChooser(false, false, tr("Save Session"), Arrays.asList(jos, joz), jos,
|
---|
[8509] | 126 | JFileChooser.FILES_ONLY, "lastDirectory");
|
---|
[4685] | 127 | }
|
---|
[5438] | 128 |
|
---|
[8913] | 129 | if (fc == null) {
|
---|
[8919] | 130 | throw new UserCancelException();
|
---|
[8913] | 131 | }
|
---|
[4685] | 132 |
|
---|
| 133 | File file = fc.getSelectedFile();
|
---|
| 134 | String fn = file.getName();
|
---|
| 135 |
|
---|
| 136 | boolean zip;
|
---|
| 137 | FileFilter ff = fc.getFileFilter();
|
---|
[11109] | 138 | if (zipRequired || joz.equals(ff)) {
|
---|
[4685] | 139 | zip = true;
|
---|
[6981] | 140 | } else if (jos.equals(ff)) {
|
---|
[4685] | 141 | zip = false;
|
---|
| 142 | } else {
|
---|
[8404] | 143 | if (Utils.hasExtension(fn, "joz")) {
|
---|
[4685] | 144 | zip = true;
|
---|
| 145 | } else {
|
---|
| 146 | zip = false;
|
---|
| 147 | }
|
---|
| 148 | }
|
---|
| 149 | if (fn.indexOf('.') == -1) {
|
---|
| 150 | file = new File(file.getPath() + (zip ? ".joz" : ".jos"));
|
---|
[8913] | 151 | if (!SaveActionBase.confirmOverwrite(file)) {
|
---|
[8919] | 152 | throw new UserCancelException();
|
---|
[8913] | 153 | }
|
---|
[4685] | 154 | }
|
---|
| 155 |
|
---|
[16438] | 156 | // TODO: resolve dependencies for layers excluded by the user
|
---|
| 157 | List<Layer> layersOut = layers.stream()
|
---|
| 158 | .filter(layer -> exporters.get(layer) != null && exporters.get(layer).shallExport())
|
---|
| 159 | .collect(Collectors.toList());
|
---|
[7509] | 160 |
|
---|
[6271] | 161 | int active = -1;
|
---|
[12636] | 162 | Layer activeLayer = getLayerManager().getActiveLayer();
|
---|
[6271] | 163 | if (activeLayer != null) {
|
---|
| 164 | active = layersOut.indexOf(activeLayer);
|
---|
| 165 | }
|
---|
[4685] | 166 |
|
---|
[6271] | 167 | SessionWriter sw = new SessionWriter(layersOut, active, exporters, dependencies, zip);
|
---|
[4685] | 168 | try {
|
---|
[18135] | 169 | Notification savingNotification = showSavingNotification(file.getName());
|
---|
[4685] | 170 | sw.write(file);
|
---|
[8802] | 171 | SaveActionBase.addToFileOpenHistory(file);
|
---|
[18135] | 172 | showSavedNotification(savingNotification, file.getName());
|
---|
[4685] | 173 | } catch (IOException ex) {
|
---|
[12620] | 174 | Logging.error(ex);
|
---|
[4685] | 175 | HelpAwareOptionPane.showMessageDialogInEDT(
|
---|
[14153] | 176 | MainApplication.getMainFrame(),
|
---|
[11848] | 177 | tr("<html>Could not save session file ''{0}''.<br>Error is:<br>{1}</html>",
|
---|
| 178 | file.getName(), Utils.escapeReservedCharactersHTML(ex.getMessage())),
|
---|
[4685] | 179 | tr("IO Error"),
|
---|
| 180 | JOptionPane.ERROR_MESSAGE,
|
---|
| 181 | null
|
---|
| 182 | );
|
---|
| 183 | }
|
---|
| 184 | }
|
---|
| 185 |
|
---|
[6245] | 186 | /**
|
---|
| 187 | * The "Save Session" dialog
|
---|
| 188 | */
|
---|
[4685] | 189 | public class SessionSaveAsDialog extends ExtendedDialog {
|
---|
| 190 |
|
---|
[6245] | 191 | /**
|
---|
| 192 | * Constructs a new {@code SessionSaveAsDialog}.
|
---|
| 193 | */
|
---|
[4685] | 194 | public SessionSaveAsDialog() {
|
---|
[14153] | 195 | super(MainApplication.getMainFrame(), tr("Save Session"), tr("Save As"), tr("Cancel"));
|
---|
[14657] | 196 | configureContextsensitiveHelp("Action/SessionSaveAs", true /* show help button */);
|
---|
[4685] | 197 | initialize();
|
---|
[12279] | 198 | setButtonIcons("save_as", "cancel");
|
---|
[5684] | 199 | setDefaultButton(1);
|
---|
[4685] | 200 | setRememberWindowGeometry(getClass().getName() + ".geometry",
|
---|
[14657] | 201 | WindowGeometry.centerInWindow(MainApplication.getMainFrame(), new Dimension(450, 450)));
|
---|
[4685] | 202 | setContent(build(), false);
|
---|
| 203 | }
|
---|
| 204 |
|
---|
[6271] | 205 | /**
|
---|
| 206 | * Initializes action.
|
---|
| 207 | */
|
---|
[6890] | 208 | public final void initialize() {
|
---|
[12636] | 209 | layers = new ArrayList<>(getLayerManager().getLayers());
|
---|
[7005] | 210 | exporters = new HashMap<>();
|
---|
| 211 | dependencies = new MultiMap<>();
|
---|
[4685] | 212 |
|
---|
[7005] | 213 | Set<Layer> noExporter = new HashSet<>();
|
---|
[4685] | 214 |
|
---|
| 215 | for (Layer layer : layers) {
|
---|
[18133] | 216 | SessionLayerExporter exporter = null;
|
---|
| 217 | try {
|
---|
| 218 | exporter = SessionWriter.getSessionLayerExporter(layer);
|
---|
| 219 | } catch (IllegalArgumentException | JosmRuntimeException e) {
|
---|
| 220 | Logging.error(e);
|
---|
| 221 | }
|
---|
[4685] | 222 | if (exporter != null) {
|
---|
| 223 | exporters.put(layer, exporter);
|
---|
| 224 | Collection<Layer> deps = exporter.getDependencies();
|
---|
| 225 | if (deps != null) {
|
---|
| 226 | dependencies.putAll(layer, deps);
|
---|
| 227 | } else {
|
---|
| 228 | dependencies.putVoid(layer);
|
---|
| 229 | }
|
---|
| 230 | } else {
|
---|
| 231 | noExporter.add(layer);
|
---|
| 232 | exporters.put(layer, null);
|
---|
| 233 | }
|
---|
| 234 | }
|
---|
| 235 |
|
---|
| 236 | int numNoExporter = 0;
|
---|
[8510] | 237 | WHILE: while (numNoExporter != noExporter.size()) {
|
---|
[4685] | 238 | numNoExporter = noExporter.size();
|
---|
| 239 | for (Layer layer : layers) {
|
---|
| 240 | if (noExporter.contains(layer)) continue;
|
---|
| 241 | for (Layer depLayer : dependencies.get(layer)) {
|
---|
| 242 | if (noExporter.contains(depLayer)) {
|
---|
| 243 | noExporter.add(layer);
|
---|
| 244 | exporters.put(layer, null);
|
---|
| 245 | break WHILE;
|
---|
| 246 | }
|
---|
| 247 | }
|
---|
| 248 | }
|
---|
| 249 | }
|
---|
| 250 | }
|
---|
| 251 |
|
---|
[6890] | 252 | protected final Component build() {
|
---|
[4685] | 253 | JPanel ip = new JPanel(new GridBagLayout());
|
---|
| 254 | for (Layer layer : layers) {
|
---|
| 255 | JPanel wrapper = new JPanel(new GridBagLayout());
|
---|
| 256 | wrapper.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
|
---|
| 257 | Component exportPanel;
|
---|
| 258 | SessionLayerExporter exporter = exporters.get(layer);
|
---|
| 259 | if (exporter == null) {
|
---|
| 260 | if (!exporters.containsKey(layer)) throw new AssertionError();
|
---|
| 261 | exportPanel = getDisabledExportPanel(layer);
|
---|
| 262 | } else {
|
---|
| 263 | exportPanel = exporter.getExportPanel();
|
---|
| 264 | }
|
---|
| 265 | wrapper.add(exportPanel, GBC.std().fill(GBC.HORIZONTAL));
|
---|
[8510] | 266 | ip.add(wrapper, GBC.eol().fill(GBC.HORIZONTAL).insets(2, 2, 4, 2));
|
---|
[4685] | 267 | }
|
---|
[8510] | 268 | ip.add(GBC.glue(0, 1), GBC.eol().fill(GBC.VERTICAL));
|
---|
[4685] | 269 | JScrollPane sp = new JScrollPane(ip);
|
---|
| 270 | sp.setBorder(BorderFactory.createEmptyBorder());
|
---|
[9062] | 271 | JPanel p = new JPanel(new GridBagLayout());
|
---|
[4685] | 272 | p.add(sp, GBC.eol().fill());
|
---|
| 273 | final JTabbedPane tabs = new JTabbedPane();
|
---|
| 274 | tabs.addTab(tr("Layers"), p);
|
---|
| 275 | return tabs;
|
---|
| 276 | }
|
---|
| 277 |
|
---|
[6981] | 278 | protected final Component getDisabledExportPanel(Layer layer) {
|
---|
[4685] | 279 | JPanel p = new JPanel(new GridBagLayout());
|
---|
| 280 | JCheckBox include = new JCheckBox();
|
---|
| 281 | include.setEnabled(false);
|
---|
[17733] | 282 | JLabel lbl = new JLabel(layer.getName(), layer.getIcon(), SwingConstants.LEADING);
|
---|
[4685] | 283 | lbl.setToolTipText(tr("No exporter for this layer"));
|
---|
[8426] | 284 | lbl.setLabelFor(include);
|
---|
[4685] | 285 | lbl.setEnabled(false);
|
---|
| 286 | p.add(include, GBC.std());
|
---|
| 287 | p.add(lbl, GBC.std());
|
---|
[8510] | 288 | p.add(GBC.glue(1, 0), GBC.std().fill(GBC.HORIZONTAL));
|
---|
[4685] | 289 | return p;
|
---|
| 290 | }
|
---|
| 291 | }
|
---|
| 292 |
|
---|
[6149] | 293 | @Override
|
---|
| 294 | protected void updateEnabledState() {
|
---|
[12630] | 295 | setEnabled(MainApplication.isDisplayingMapView());
|
---|
[6149] | 296 | }
|
---|
[9629] | 297 |
|
---|
| 298 | @Override
|
---|
| 299 | public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
|
---|
| 300 | updateEnabledState();
|
---|
[9631] | 301 | }
|
---|
[4685] | 302 | }
|
---|