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