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.WindowGeometry;
044
045/**
046 * Saves a JOSM session
047 * @since 4685
048 */
049public class SessionSaveAsAction extends DiskAccessAction {
050
051    private List<Layer> layers;
052    private Map<Layer, SessionLayerExporter> exporters;
053    private MultiMap<Layer, Layer> dependencies;
054
055    /**
056     * Constructs a new {@code SessionSaveAsAction}.
057     */
058    public SessionSaveAsAction() {
059        super(tr("Save Session As..."), "save_as", tr("Save the current session to a new file."), null, true, "save_as-session", true);
060        putValue("help", ht("/Action/SessionSaveAs"));
061    }
062
063    @Override
064    public void actionPerformed(ActionEvent e) {
065        if (!isEnabled()) {
066            return;
067        }
068
069        SessionSaveAsDialog dlg = new SessionSaveAsDialog();
070        dlg.showDialog();
071        if (dlg.getValue() != 1) return;
072
073        boolean zipRequired = false;
074        for (Layer l : layers) {
075            SessionLayerExporter ex = exporters.get(l);
076            if (ex != null && ex.requiresZip()) {
077                zipRequired = true;
078                break;
079            }
080        }
081
082        FileFilter joz = new ExtensionFileFilter("joz", "joz", tr("Session file (archive) (*.joz)"));
083        FileFilter jos = new ExtensionFileFilter("jos", "jos", tr("Session file (*.jos)"));
084
085        AbstractFileChooser fc;
086
087        if (zipRequired) {
088            fc = createAndOpenFileChooser(false, false, tr("Save session"), joz, JFileChooser.FILES_ONLY, "lastDirectory");
089        } else {
090            fc = createAndOpenFileChooser(false, false, tr("Save session"), Arrays.asList(new FileFilter[]{jos, joz}), jos, JFileChooser.FILES_ONLY, "lastDirectory");
091        }
092
093        if (fc == null)
094            return;
095
096        File file = fc.getSelectedFile();
097        String fn = file.getName();
098
099        boolean zip;
100        FileFilter ff = fc.getFileFilter();
101        if (zipRequired) {
102            zip = true;
103        } else if (joz.equals(ff)) {
104            zip = true;
105        } else if (jos.equals(ff)) {
106            zip = false;
107        } else {
108            if (fn.toLowerCase().endsWith(".joz")) {
109                zip = true;
110            } else {
111                zip = false;
112            }
113        }
114        if (fn.indexOf('.') == -1) {
115            file = new File(file.getPath() + (zip ? ".joz" : ".jos"));
116            if (!SaveActionBase.confirmOverwrite(file))
117                return;
118        }
119
120        List<Layer> layersOut = new ArrayList<>();
121        for (Layer layer : layers) {
122            if (exporters.get(layer) == null || !exporters.get(layer).shallExport()) continue;
123            // TODO: resolve dependencies for layers excluded by the user
124            layersOut.add(layer);
125        }
126
127        int active = -1;
128        Layer activeLayer = Main.map.mapView.getActiveLayer();
129        if (activeLayer != null) {
130            active = layersOut.indexOf(activeLayer);
131        }
132
133        SessionWriter sw = new SessionWriter(layersOut, active, exporters, dependencies, zip);
134        try {
135            sw.write(file);
136        } catch (IOException ex) {
137            Main.error(ex);
138            HelpAwareOptionPane.showMessageDialogInEDT(
139                    Main.parent,
140                    tr("<html>Could not save session file ''{0}''.<br>Error is:<br>{1}</html>", file.getName(), ex.getMessage()),
141                    tr("IO Error"),
142                    JOptionPane.ERROR_MESSAGE,
143                    null
144            );
145        }
146    }
147
148    /**
149     * The "Save Session" dialog
150     */
151    public class SessionSaveAsDialog extends ExtendedDialog {
152
153        /**
154         * Constructs a new {@code SessionSaveAsDialog}.
155         */
156        public SessionSaveAsDialog() {
157            super(Main.parent, tr("Save Session"), new String[] {tr("Save As"), tr("Cancel")});
158            initialize();
159            setButtonIcons(new String[] {"save_as", "cancel"});
160            setDefaultButton(1);
161            setRememberWindowGeometry(getClass().getName() + ".geometry",
162                    WindowGeometry.centerInWindow(Main.parent, new Dimension(350, 450)));
163            setContent(build(), false);
164        }
165
166        /**
167         * Initializes action.
168         */
169        public final void initialize() {
170            layers = new ArrayList<>(Main.map.mapView.getAllLayersAsList());
171            exporters = new HashMap<>();
172            dependencies = new MultiMap<>();
173
174            Set<Layer> noExporter = new HashSet<>();
175
176            for (Layer layer : layers) {
177                SessionLayerExporter exporter = SessionWriter.getSessionLayerExporter(layer);
178                if (exporter != null) {
179                    exporters.put(layer, exporter);
180                    Collection<Layer> deps = exporter.getDependencies();
181                    if (deps != null) {
182                        dependencies.putAll(layer, deps);
183                    } else {
184                        dependencies.putVoid(layer);
185                    }
186                } else {
187                    noExporter.add(layer);
188                    exporters.put(layer, null);
189                }
190            }
191
192            int numNoExporter = 0;
193            WHILE:while (numNoExporter != noExporter.size()) {
194                numNoExporter = noExporter.size();
195                for (Layer layer : layers) {
196                    if (noExporter.contains(layer)) continue;
197                    for (Layer depLayer : dependencies.get(layer)) {
198                        if (noExporter.contains(depLayer)) {
199                            noExporter.add(layer);
200                            exporters.put(layer, null);
201                            break WHILE;
202                        }
203                    }
204                }
205            }
206        }
207
208        protected final Component build() {
209            JPanel p = new JPanel(new GridBagLayout());
210            JPanel ip = new JPanel(new GridBagLayout());
211            for (Layer layer : layers) {
212                JPanel wrapper = new JPanel(new GridBagLayout());
213                wrapper.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
214                Component exportPanel;
215                SessionLayerExporter exporter = exporters.get(layer);
216                if (exporter == null) {
217                    if (!exporters.containsKey(layer)) throw new AssertionError();
218                    exportPanel = getDisabledExportPanel(layer);
219                } else {
220                    exportPanel = exporter.getExportPanel();
221                }
222                wrapper.add(exportPanel, GBC.std().fill(GBC.HORIZONTAL));
223                ip.add(wrapper, GBC.eol().fill(GBC.HORIZONTAL).insets(2,2,4,2));
224            }
225            ip.add(GBC.glue(0,1), GBC.eol().fill(GBC.VERTICAL));
226            JScrollPane sp = new JScrollPane(ip);
227            sp.setBorder(BorderFactory.createEmptyBorder());
228            p.add(sp, GBC.eol().fill());
229            final JTabbedPane tabs = new JTabbedPane();
230            tabs.addTab(tr("Layers"), p);
231            return tabs;
232        }
233
234        protected final Component getDisabledExportPanel(Layer layer) {
235            JPanel p = new JPanel(new GridBagLayout());
236            JCheckBox include = new JCheckBox();
237            include.setEnabled(false);
238            JLabel lbl = new JLabel(layer.getName(), layer.getIcon(), SwingConstants.LEFT);
239            lbl.setToolTipText(tr("No exporter for this layer"));
240            lbl.setEnabled(false);
241            p.add(include, GBC.std());
242            p.add(lbl, GBC.std());
243            p.add(GBC.glue(1,0), GBC.std().fill(GBC.HORIZONTAL));
244            return p;
245        }
246    }
247
248    @Override
249    protected void updateEnabledState() {
250        setEnabled(Main.isDisplayingMapView());
251    }
252}