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