001//License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.actions;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.event.ActionEvent;
007import java.io.File;
008import java.io.IOException;
009import java.util.Collection;
010import java.util.LinkedList;
011import java.util.List;
012
013import javax.swing.JFileChooser;
014import javax.swing.JOptionPane;
015import javax.swing.filechooser.FileFilter;
016
017import org.openstreetmap.josm.Main;
018import org.openstreetmap.josm.gui.ExtendedDialog;
019import org.openstreetmap.josm.gui.layer.Layer;
020import org.openstreetmap.josm.gui.layer.OsmDataLayer;
021import org.openstreetmap.josm.io.FileExporter;
022import org.openstreetmap.josm.tools.Shortcut;
023
024public abstract class SaveActionBase extends DiskAccessAction {
025    private File file;
026
027    public SaveActionBase(String name, String iconName, String tooltip, Shortcut shortcut) {
028        super(name, iconName, tooltip, shortcut);
029    }
030
031    @Override
032    public void actionPerformed(ActionEvent e) {
033        if (!isEnabled())
034            return;
035        boolean saved = doSave();
036        if (saved) {
037            addToFileOpenHistory();
038        }
039    }
040
041    public boolean doSave() {
042        if (Main.isDisplayingMapView()) {
043            Layer layer = Main.map.mapView.getActiveLayer();
044            if (layer != null && layer.isSavable()) {
045                return doSave(layer);
046            }
047        }
048        return false;
049    }
050
051    public boolean doSave(Layer layer) {
052        if(!layer.checkSaveConditions())
053            return false;
054        file = getFile(layer);
055        return doInternalSave(layer, file);
056    }
057
058    /**
059     * Saves a layer to a given file.
060     * @param layer The layer to save
061     * @param file The destination file
062     * @param checkSaveConditions if {@code true}, checks preconditions before saving. Set it to {@code false} to skip it
063     * if preconditions have already been checked (as this check can prompt UI dialog in EDT it may be best in some cases
064     * to do it earlier).
065     * @return {@code true} if the layer has been successfully saved, {@code false} otherwise
066     * @since 7204
067     */
068    public static boolean doSave(Layer layer, File file, boolean checkSaveConditions) {
069        if (checkSaveConditions && !layer.checkSaveConditions())
070            return false;
071        return doInternalSave(layer, file);
072    }
073
074    private static boolean doInternalSave(Layer layer, File file) {
075        if (file == null)
076            return false;
077
078        try {
079            boolean exported = false;
080            boolean canceled = false;
081            for (FileExporter exporter : ExtensionFileFilter.exporters) {
082                if (exporter.acceptFile(file, layer)) {
083                    exporter.exportData(file, layer);
084                    exported = true;
085                    canceled = exporter.isCanceled();
086                    break;
087                }
088            }
089            if (!exported) {
090                JOptionPane.showMessageDialog(Main.parent, tr("No Exporter found! Nothing saved."), tr("Warning"),
091                        JOptionPane.WARNING_MESSAGE);
092                return false;
093            } else if (canceled) {
094                return false;
095            }
096            layer.setName(file.getName());
097            layer.setAssociatedFile(file);
098            if (layer instanceof OsmDataLayer) {
099                ((OsmDataLayer) layer).onPostSaveToFile();
100            }
101            Main.parent.repaint();
102        } catch (IOException e) {
103            Main.error(e);
104            return false;
105        }
106        return true;
107    }
108
109    protected abstract File getFile(Layer layer);
110
111    /**
112     * Refreshes the enabled state
113     *
114     */
115    @Override
116    protected void updateEnabledState() {
117        boolean check = Main.isDisplayingMapView()
118        && Main.map.mapView.getActiveLayer() != null;
119        if(!check) {
120            setEnabled(false);
121            return;
122        }
123        Layer layer = Main.map.mapView.getActiveLayer();
124        setEnabled(layer != null && layer.isSavable());
125    }
126
127    /**
128     * Creates a new "Save" dialog for a single {@link ExtensionFileFilter} and makes it visible.<br>
129     * When the user has chosen a file, checks the file extension, and confirms overwrite if needed.
130     *
131     * @param title The dialog title
132     * @param filter The dialog file filter
133     * @return The output {@code File}
134     * @since 5456
135     * @see DiskAccessAction#createAndOpenFileChooser(boolean, boolean, String, FileFilter, int, String)
136     */
137    public static File createAndOpenSaveFileChooser(String title, ExtensionFileFilter filter) {
138        JFileChooser fc = createAndOpenFileChooser(false, false, title, filter, JFileChooser.FILES_ONLY, null);
139        return checkFileAndConfirmOverWrite(fc, filter.getDefaultExtension());
140    }
141
142    /**
143     * Creates a new "Save" dialog for a given file extension and makes it visible.<br>
144     * When the user has chosen a file, checks the file extension, and confirms overwrite if needed.
145     *
146     * @param title The dialog title
147     * @param extension The file extension
148     * @return The output {@code File}
149     * @see DiskAccessAction#createAndOpenFileChooser(boolean, boolean, String, String)
150     */
151    public static File createAndOpenSaveFileChooser(String title, String extension) {
152        JFileChooser fc = createAndOpenFileChooser(false, false, title, extension);
153        return checkFileAndConfirmOverWrite(fc, extension);
154    }
155
156    private static File checkFileAndConfirmOverWrite(JFileChooser fc, String extension) {
157        if (fc == null) return null;
158        File file = fc.getSelectedFile();
159
160        FileFilter ff = fc.getFileFilter();
161        if (!ff.accept(file)) {
162            // Extension of another filefilter given ?
163            for (FileFilter cff : fc.getChoosableFileFilters()) {
164                if (cff.accept(file)) {
165                    fc.setFileFilter(cff);
166                    return file;
167                }
168            }
169            // No filefilter accepts current filename, add default extension
170            String fn = file.getPath();
171            if (ff instanceof ExtensionFileFilter) {
172                fn += "." + ((ExtensionFileFilter)ff).getDefaultExtension();
173            } else if (extension != null) {
174                fn += "." + extension;
175            }
176            file = new File(fn);
177            if (!confirmOverwrite(file))
178                return null;
179        }
180        return file;
181    }
182
183    public static boolean confirmOverwrite(File file) {
184        if (file == null || (file.exists())) {
185            ExtendedDialog dialog = new ExtendedDialog(
186                    Main.parent,
187                    tr("Overwrite"),
188                    new String[] {tr("Overwrite"), tr("Cancel")}
189            );
190            dialog.setContent(tr("File exists. Overwrite?"));
191            dialog.setButtonIcons(new String[] {"save_as.png", "cancel.png"});
192            dialog.showDialog();
193            return (dialog.getValue() == 1);
194        }
195        return true;
196    }
197
198    protected void addToFileOpenHistory() {
199        String filepath;
200        try {
201            filepath = file.getCanonicalPath();
202        } catch (IOException ign) {
203            return;
204        }
205
206        int maxsize = Math.max(0, Main.pref.getInteger("file-open.history.max-size", 15));
207        Collection<String> oldHistory = Main.pref.getCollection("file-open.history");
208        List<String> history = new LinkedList<>(oldHistory);
209        history.remove(filepath);
210        history.add(0, filepath);
211        Main.pref.putCollectionBounded("file-open.history", maxsize, history);
212    }
213}