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            Main.trace(ignore);
084        }
085    }
086
087    /**
088     * Attempts to save the session.
089     * @throws UserCancelException when the user has cancelled the save process.
090     * @since 8913
091     */
092    public void saveSession() throws UserCancelException {
093        if (!isEnabled()) {
094            return;
095        }
096
097        SessionSaveAsDialog dlg = new SessionSaveAsDialog();
098        dlg.showDialog();
099        if (dlg.getValue() != 1) {
100            throw new UserCancelException();
101        }
102
103        boolean zipRequired = false;
104        for (Layer l : layers) {
105            SessionLayerExporter ex = exporters.get(l);
106            if (ex != null && ex.requiresZip()) {
107                zipRequired = true;
108                break;
109            }
110        }
111
112        FileFilter joz = new ExtensionFileFilter("joz", "joz", tr("Session file (archive) (*.joz)"));
113        FileFilter jos = new ExtensionFileFilter("jos", "jos", tr("Session file (*.jos)"));
114
115        AbstractFileChooser fc;
116
117        if (zipRequired) {
118            fc = createAndOpenFileChooser(false, false, tr("Save session"), joz, JFileChooser.FILES_ONLY, "lastDirectory");
119        } else {
120            fc = createAndOpenFileChooser(false, false, tr("Save session"), Arrays.asList(new FileFilter[]{jos, joz}), jos,
121                    JFileChooser.FILES_ONLY, "lastDirectory");
122        }
123
124        if (fc == null) {
125            throw new UserCancelException();
126        }
127
128        File file = fc.getSelectedFile();
129        String fn = file.getName();
130
131        boolean zip;
132        FileFilter ff = fc.getFileFilter();
133        if (zipRequired) {
134            zip = true;
135        } else if (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 = Main.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            Main.error(ex);
172            HelpAwareOptionPane.showMessageDialogInEDT(
173                    Main.parent,
174                    tr("<html>Could not save session file ''{0}''.<br>Error is:<br>{1}</html>", file.getName(), ex.getMessage()),
175                    tr("IO Error"),
176                    JOptionPane.ERROR_MESSAGE,
177                    null
178            );
179        }
180    }
181
182    /**
183     * The "Save Session" dialog
184     */
185    public class SessionSaveAsDialog extends ExtendedDialog {
186
187        /**
188         * Constructs a new {@code SessionSaveAsDialog}.
189         */
190        public SessionSaveAsDialog() {
191            super(Main.parent, tr("Save Session"), new String[] {tr("Save As"), tr("Cancel")});
192            initialize();
193            setButtonIcons(new String[] {"save_as", "cancel"});
194            setDefaultButton(1);
195            setRememberWindowGeometry(getClass().getName() + ".geometry",
196                    WindowGeometry.centerInWindow(Main.parent, new Dimension(350, 450)));
197            setContent(build(), false);
198        }
199
200        /**
201         * Initializes action.
202         */
203        public final void initialize() {
204            layers = new ArrayList<>(Main.getLayerManager().getLayers());
205            exporters = new HashMap<>();
206            dependencies = new MultiMap<>();
207
208            Set<Layer> noExporter = new HashSet<>();
209
210            for (Layer layer : layers) {
211                SessionLayerExporter exporter = SessionWriter.getSessionLayerExporter(layer);
212                if (exporter != null) {
213                    exporters.put(layer, exporter);
214                    Collection<Layer> deps = exporter.getDependencies();
215                    if (deps != null) {
216                        dependencies.putAll(layer, deps);
217                    } else {
218                        dependencies.putVoid(layer);
219                    }
220                } else {
221                    noExporter.add(layer);
222                    exporters.put(layer, null);
223                }
224            }
225
226            int numNoExporter = 0;
227            WHILE: while (numNoExporter != noExporter.size()) {
228                numNoExporter = noExporter.size();
229                for (Layer layer : layers) {
230                    if (noExporter.contains(layer)) continue;
231                    for (Layer depLayer : dependencies.get(layer)) {
232                        if (noExporter.contains(depLayer)) {
233                            noExporter.add(layer);
234                            exporters.put(layer, null);
235                            break WHILE;
236                        }
237                    }
238                }
239            }
240        }
241
242        protected final Component build() {
243            JPanel ip = new JPanel(new GridBagLayout());
244            for (Layer layer : layers) {
245                JPanel wrapper = new JPanel(new GridBagLayout());
246                wrapper.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
247                Component exportPanel;
248                SessionLayerExporter exporter = exporters.get(layer);
249                if (exporter == null) {
250                    if (!exporters.containsKey(layer)) throw new AssertionError();
251                    exportPanel = getDisabledExportPanel(layer);
252                } else {
253                    exportPanel = exporter.getExportPanel();
254                }
255                wrapper.add(exportPanel, GBC.std().fill(GBC.HORIZONTAL));
256                ip.add(wrapper, GBC.eol().fill(GBC.HORIZONTAL).insets(2, 2, 4, 2));
257            }
258            ip.add(GBC.glue(0, 1), GBC.eol().fill(GBC.VERTICAL));
259            JScrollPane sp = new JScrollPane(ip);
260            sp.setBorder(BorderFactory.createEmptyBorder());
261            JPanel p = new JPanel(new GridBagLayout());
262            p.add(sp, GBC.eol().fill());
263            final JTabbedPane tabs = new JTabbedPane();
264            tabs.addTab(tr("Layers"), p);
265            return tabs;
266        }
267
268        protected final Component getDisabledExportPanel(Layer layer) {
269            JPanel p = new JPanel(new GridBagLayout());
270            JCheckBox include = new JCheckBox();
271            include.setEnabled(false);
272            JLabel lbl = new JLabel(layer.getName(), layer.getIcon(), SwingConstants.LEFT);
273            lbl.setToolTipText(tr("No exporter for this layer"));
274            lbl.setLabelFor(include);
275            lbl.setEnabled(false);
276            p.add(include, GBC.std());
277            p.add(lbl, GBC.std());
278            p.add(GBC.glue(1, 0), GBC.std().fill(GBC.HORIZONTAL));
279            return p;
280        }
281    }
282
283    @Override
284    protected void updateEnabledState() {
285        setEnabled(Main.isDisplayingMapView());
286    }
287
288    @Override
289    public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
290        updateEnabledState();
291    }
292}