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