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