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