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