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.gui.widgets.FileChooserManager;
023import org.openstreetmap.josm.io.FileExporter;
024import org.openstreetmap.josm.tools.Shortcut;
025
026public abstract class SaveActionBase extends DiskAccessAction {
027    private File file;
028
029    public SaveActionBase(String name, String iconName, String tooltip, Shortcut shortcut) {
030        super(name, iconName, tooltip, shortcut);
031    }
032
033    @Override
034    public void actionPerformed(ActionEvent e) {
035        if (!isEnabled())
036            return;
037        boolean saved = doSave();
038    }
039
040    public boolean doSave() {
041        if (Main.isDisplayingMapView()) {
042            Layer layer = Main.map.mapView.getActiveLayer();
043            if (layer != null && layer.isSavable()) {
044                return doSave(layer);
045            }
046        }
047        return false;
048    }
049
050    public boolean doSave(Layer layer) {
051        if (!layer.checkSaveConditions())
052            return false;
053        file = getFile(layer);
054        return doInternalSave(layer, file);
055    }
056
057    /**
058     * Saves a layer to a given file.
059     * @param layer The layer to save
060     * @param file The destination file
061     * @param checkSaveConditions if {@code true}, checks preconditions before saving. Set it to {@code false} to skip it
062     * if preconditions have already been checked (as this check can prompt UI dialog in EDT it may be best in some cases
063     * to do it earlier).
064     * @return {@code true} if the layer has been successfully saved, {@code false} otherwise
065     * @since 7204
066     */
067    public static boolean doSave(Layer layer, File file, boolean checkSaveConditions) {
068        if (checkSaveConditions && !layer.checkSaveConditions())
069            return false;
070        return doInternalSave(layer, file);
071    }
072
073    private static boolean doInternalSave(Layer layer, File file) {
074        if (file == null)
075            return false;
076
077        try {
078            boolean exported = false;
079            boolean canceled = false;
080            for (FileExporter exporter : ExtensionFileFilter.exporters) {
081                if (exporter.acceptFile(file, layer)) {
082                    exporter.exportData(file, layer);
083                    exported = true;
084                    canceled = exporter.isCanceled();
085                    break;
086                }
087            }
088            if (!exported) {
089                JOptionPane.showMessageDialog(Main.parent, tr("No Exporter found! Nothing saved."), tr("Warning"),
090                        JOptionPane.WARNING_MESSAGE);
091                return false;
092            } else if (canceled) {
093                return false;
094            }
095            layer.setName(file.getName());
096            layer.setAssociatedFile(file);
097            if (layer instanceof OsmDataLayer) {
098                ((OsmDataLayer) layer).onPostSaveToFile();
099            }
100            Main.parent.repaint();
101        } catch (IOException e) {
102            Main.error(e);
103            return false;
104        }
105        addToFileOpenHistory(file);
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     * @see DiskAccessAction#createAndOpenFileChooser(boolean, boolean, String, FileFilter, int, String)
135     * @since 5456
136     */
137    public static File createAndOpenSaveFileChooser(String title, ExtensionFileFilter filter) {
138        AbstractFileChooser 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        AbstractFileChooser fc = createAndOpenFileChooser(false, false, title, extension);
153        return checkFileAndConfirmOverWrite(fc, extension);
154    }
155
156    private static File checkFileAndConfirmOverWrite(AbstractFileChooser 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            // Confirm overwrite, except for OSX native file dialogs which already ask for confirmation (see #11362)
178            if (!(Main.isPlatformOsx() && FileChooserManager.PROP_USE_NATIVE_FILE_DIALOG.get()) && !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", "cancel"});
193            dialog.showDialog();
194            return dialog.getValue() == 1;
195        }
196        return true;
197    }
198
199    static void addToFileOpenHistory(File file) {
200        final 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}