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