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