001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.actions;
003
004import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
005import static org.openstreetmap.josm.tools.I18n.tr;
006
007import java.awt.Component;
008import java.awt.Dimension;
009import java.awt.GridBagLayout;
010import java.awt.event.ActionEvent;
011import java.io.File;
012import java.io.IOException;
013import java.util.ArrayList;
014import java.util.Arrays;
015import java.util.Collection;
016import java.util.HashMap;
017import java.util.HashSet;
018import java.util.List;
019import java.util.Map;
020import java.util.Set;
021
022import javax.swing.BorderFactory;
023import javax.swing.JCheckBox;
024import javax.swing.JFileChooser;
025import javax.swing.JLabel;
026import javax.swing.JOptionPane;
027import javax.swing.JPanel;
028import javax.swing.JScrollPane;
029import javax.swing.JTabbedPane;
030import javax.swing.SwingConstants;
031import javax.swing.border.EtchedBorder;
032import javax.swing.filechooser.FileFilter;
033
034import org.openstreetmap.josm.Main;
035import org.openstreetmap.josm.gui.ExtendedDialog;
036import org.openstreetmap.josm.gui.HelpAwareOptionPane;
037import org.openstreetmap.josm.gui.layer.Layer;
038import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
039import org.openstreetmap.josm.io.session.SessionLayerExporter;
040import org.openstreetmap.josm.io.session.SessionWriter;
041import org.openstreetmap.josm.tools.GBC;
042import org.openstreetmap.josm.tools.MultiMap;
043import org.openstreetmap.josm.tools.UserCancelException;
044import org.openstreetmap.josm.tools.Utils;
045import org.openstreetmap.josm.tools.WindowGeometry;
046
047/**
048 * Saves a JOSM session
049 * @since 4685
050 */
051public class SessionSaveAsAction extends DiskAccessAction {
052
053    private transient List<Layer> layers;
054    private transient Map<Layer, SessionLayerExporter> exporters;
055    private transient MultiMap<Layer, Layer> dependencies;
056
057    /**
058     * Constructs a new {@code SessionSaveAsAction}.
059     */
060    public SessionSaveAsAction() {
061        super(tr("Save Session As..."), "session", tr("Save the current session to a new file."), null, true, "save_as-session", true);
062        putValue("help", ht("/Action/SessionSaveAs"));
063    }
064
065    @Override
066    public void actionPerformed(ActionEvent e) {
067        try {
068            saveSession();
069        } catch (UserCancelException ignore) {
070            if (Main.isTraceEnabled()) {
071                Main.trace(ignore.getMessage());
072            }
073        }
074    }
075
076    /**
077     * Attempts to save the session.
078     * @throws UserCancelException when the user has cancelled the save process.
079     * @since 8913
080     */
081    public void saveSession() throws UserCancelException {
082        if (!isEnabled()) {
083            return;
084        }
085
086        SessionSaveAsDialog dlg = new SessionSaveAsDialog();
087        dlg.showDialog();
088        if (dlg.getValue() != 1) {
089            throw new UserCancelException();
090        }
091
092        boolean zipRequired = false;
093        for (Layer l : layers) {
094            SessionLayerExporter ex = exporters.get(l);
095            if (ex != null && ex.requiresZip()) {
096                zipRequired = true;
097                break;
098            }
099        }
100
101        FileFilter joz = new ExtensionFileFilter("joz", "joz", tr("Session file (archive) (*.joz)"));
102        FileFilter jos = new ExtensionFileFilter("jos", "jos", tr("Session file (*.jos)"));
103
104        AbstractFileChooser fc;
105
106        if (zipRequired) {
107            fc = createAndOpenFileChooser(false, false, tr("Save session"), joz, JFileChooser.FILES_ONLY, "lastDirectory");
108        } else {
109            fc = createAndOpenFileChooser(false, false, tr("Save session"), Arrays.asList(new FileFilter[]{jos, joz}), jos,
110                    JFileChooser.FILES_ONLY, "lastDirectory");
111        }
112
113        if (fc == null) {
114            throw new UserCancelException();
115        }
116
117        File file = fc.getSelectedFile();
118        String fn = file.getName();
119
120        boolean zip;
121        FileFilter ff = fc.getFileFilter();
122        if (zipRequired) {
123            zip = true;
124        } else if (joz.equals(ff)) {
125            zip = true;
126        } else if (jos.equals(ff)) {
127            zip = false;
128        } else {
129            if (Utils.hasExtension(fn, "joz")) {
130                zip = true;
131            } else {
132                zip = false;
133            }
134        }
135        if (fn.indexOf('.') == -1) {
136            file = new File(file.getPath() + (zip ? ".joz" : ".jos"));
137            if (!SaveActionBase.confirmOverwrite(file)) {
138                throw new UserCancelException();
139            }
140        }
141
142        List<Layer> layersOut = new ArrayList<>();
143        for (Layer layer : layers) {
144            if (exporters.get(layer) == null || !exporters.get(layer).shallExport()) continue;
145            // TODO: resolve dependencies for layers excluded by the user
146            layersOut.add(layer);
147        }
148
149        int active = -1;
150        Layer activeLayer = Main.map.mapView.getActiveLayer();
151        if (activeLayer != null) {
152            active = layersOut.indexOf(activeLayer);
153        }
154
155        SessionWriter sw = new SessionWriter(layersOut, active, exporters, dependencies, zip);
156        try {
157            sw.write(file);
158            SaveActionBase.addToFileOpenHistory(file);
159        } catch (IOException ex) {
160            Main.error(ex);
161            HelpAwareOptionPane.showMessageDialogInEDT(
162                    Main.parent,
163                    tr("<html>Could not save session file ''{0}''.<br>Error is:<br>{1}</html>", file.getName(), ex.getMessage()),
164                    tr("IO Error"),
165                    JOptionPane.ERROR_MESSAGE,
166                    null
167            );
168        }
169    }
170
171    /**
172     * The "Save Session" dialog
173     */
174    public class SessionSaveAsDialog extends ExtendedDialog {
175
176        /**
177         * Constructs a new {@code SessionSaveAsDialog}.
178         */
179        public SessionSaveAsDialog() {
180            super(Main.parent, tr("Save Session"), new String[] {tr("Save As"), tr("Cancel")});
181            initialize();
182            setButtonIcons(new String[] {"save_as", "cancel"});
183            setDefaultButton(1);
184            setRememberWindowGeometry(getClass().getName() + ".geometry",
185                    WindowGeometry.centerInWindow(Main.parent, new Dimension(350, 450)));
186            setContent(build(), false);
187        }
188
189        /**
190         * Initializes action.
191         */
192        public final void initialize() {
193            layers = new ArrayList<>(Main.map.mapView.getAllLayersAsList());
194            exporters = new HashMap<>();
195            dependencies = new MultiMap<>();
196
197            Set<Layer> noExporter = new HashSet<>();
198
199            for (Layer layer : layers) {
200                SessionLayerExporter exporter = SessionWriter.getSessionLayerExporter(layer);
201                if (exporter != null) {
202                    exporters.put(layer, exporter);
203                    Collection<Layer> deps = exporter.getDependencies();
204                    if (deps != null) {
205                        dependencies.putAll(layer, deps);
206                    } else {
207                        dependencies.putVoid(layer);
208                    }
209                } else {
210                    noExporter.add(layer);
211                    exporters.put(layer, null);
212                }
213            }
214
215            int numNoExporter = 0;
216            WHILE: while (numNoExporter != noExporter.size()) {
217                numNoExporter = noExporter.size();
218                for (Layer layer : layers) {
219                    if (noExporter.contains(layer)) continue;
220                    for (Layer depLayer : dependencies.get(layer)) {
221                        if (noExporter.contains(depLayer)) {
222                            noExporter.add(layer);
223                            exporters.put(layer, null);
224                            break WHILE;
225                        }
226                    }
227                }
228            }
229        }
230
231        protected final Component build() {
232            JPanel p = new JPanel(new GridBagLayout());
233            JPanel ip = new JPanel(new GridBagLayout());
234            for (Layer layer : layers) {
235                JPanel wrapper = new JPanel(new GridBagLayout());
236                wrapper.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
237                Component exportPanel;
238                SessionLayerExporter exporter = exporters.get(layer);
239                if (exporter == null) {
240                    if (!exporters.containsKey(layer)) throw new AssertionError();
241                    exportPanel = getDisabledExportPanel(layer);
242                } else {
243                    exportPanel = exporter.getExportPanel();
244                }
245                wrapper.add(exportPanel, GBC.std().fill(GBC.HORIZONTAL));
246                ip.add(wrapper, GBC.eol().fill(GBC.HORIZONTAL).insets(2, 2, 4, 2));
247            }
248            ip.add(GBC.glue(0, 1), GBC.eol().fill(GBC.VERTICAL));
249            JScrollPane sp = new JScrollPane(ip);
250            sp.setBorder(BorderFactory.createEmptyBorder());
251            p.add(sp, GBC.eol().fill());
252            final JTabbedPane tabs = new JTabbedPane();
253            tabs.addTab(tr("Layers"), p);
254            return tabs;
255        }
256
257        protected final Component getDisabledExportPanel(Layer layer) {
258            JPanel p = new JPanel(new GridBagLayout());
259            JCheckBox include = new JCheckBox();
260            include.setEnabled(false);
261            JLabel lbl = new JLabel(layer.getName(), layer.getIcon(), SwingConstants.LEFT);
262            lbl.setToolTipText(tr("No exporter for this layer"));
263            lbl.setLabelFor(include);
264            lbl.setEnabled(false);
265            p.add(include, GBC.std());
266            p.add(lbl, GBC.std());
267            p.add(GBC.glue(1, 0), GBC.std().fill(GBC.HORIZONTAL));
268            return p;
269        }
270    }
271
272    @Override
273    protected void updateEnabledState() {
274        setEnabled(Main.isDisplayingMapView());
275    }
276}