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}