001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.widgets; 003 004import java.awt.Component; 005import java.io.File; 006import java.util.Collection; 007import java.util.Collections; 008 009import javax.swing.JFileChooser; 010import javax.swing.filechooser.FileFilter; 011 012import org.openstreetmap.josm.Main; 013import org.openstreetmap.josm.actions.DiskAccessAction; 014import org.openstreetmap.josm.actions.ExtensionFileFilter; 015import org.openstreetmap.josm.actions.SaveActionBase; 016import org.openstreetmap.josm.data.preferences.BooleanProperty; 017 018/** 019 * A chained utility class used to create and open {@link AbstractFileChooser} dialogs.<br> 020 * Use only this class if you need to control specifically your AbstractFileChooser dialog.<br> 021 * <p> 022 * A simpler usage is to call the {@link DiskAccessAction#createAndOpenFileChooser} methods. 023 * 024 * @since 5438 (creation) 025 * @since 7578 (rename) 026 */ 027public class FileChooserManager { 028 029 /** 030 * Property to enable use of native file dialogs. 031 */ 032 public static final BooleanProperty PROP_USE_NATIVE_FILE_DIALOG = new BooleanProperty("use.native.file.dialog", 033 // Native dialogs do not support file filters, so do not set them as default, except for OS X where they never worked 034 Main.isPlatformOsx()); 035 036 private final boolean open; 037 private final String lastDirProperty; 038 private final String curDir; 039 040 private boolean multiple; 041 private String title; 042 private Collection<? extends FileFilter> filters; 043 private FileFilter defaultFilter; 044 private int selectionMode = JFileChooser.FILES_ONLY; 045 private String extension; 046 private boolean allTypes; 047 private File file; 048 049 private AbstractFileChooser fc; 050 051 /** 052 * Creates a new {@code FileChooserManager} with default values. 053 * @see #createFileChooser 054 */ 055 public FileChooserManager() { 056 this(false, null, null); 057 } 058 059 /** 060 * Creates a new {@code FileChooserManager}. 061 * @param open If true, "Open File" dialogs will be created. If false, "Save File" dialogs will be created. 062 * @see #createFileChooser 063 */ 064 public FileChooserManager(boolean open) { 065 this(open, null); 066 } 067 068 // CHECKSTYLE.OFF: LineLength 069 070 /** 071 * Creates a new {@code FileChooserManager}. 072 * @param open If true, "Open File" dialogs will be created. If false, "Save File" dialogs will be created. 073 * @param lastDirProperty The name of the property used to get the last directory. This directory is used to initialize the AbstractFileChooser. 074 * Then, if the user effectively chooses a file or a directory, this property will be updated to the directory path. 075 * @see #createFileChooser 076 */ 077 public FileChooserManager(boolean open, String lastDirProperty) { 078 this(open, lastDirProperty, null); 079 } 080 081 /** 082 * Creates a new {@code FileChooserManager}. 083 * @param open If true, "Open File" dialogs will be created. If false, "Save File" dialogs will be created. 084 * @param lastDirProperty The name of the property used to get the last directory. This directory is used to initialize the AbstractFileChooser. 085 * Then, if the user effectively chooses a file or a directory, this property will be updated to the directory path. 086 * @param defaultDir The default directory used to initialize the AbstractFileChooser if the {@code lastDirProperty} property value is missing. 087 * @see #createFileChooser 088 */ 089 public FileChooserManager(boolean open, String lastDirProperty, String defaultDir) { 090 this.open = open; 091 this.lastDirProperty = lastDirProperty == null || lastDirProperty.isEmpty() ? "lastDirectory" : lastDirProperty; 092 this.curDir = Main.pref.get(this.lastDirProperty).isEmpty() ? 093 defaultDir == null || defaultDir.isEmpty() ? "." : defaultDir 094 : Main.pref.get(this.lastDirProperty); 095 } 096 097 // CHECKSTYLE.ON: LineLength 098 099 /** 100 * Replies the {@code AbstractFileChooser} that has been previously created. 101 * @return The {@code AbstractFileChooser} that has been previously created, or {@code null} if it has not been created yet. 102 * @see #createFileChooser 103 */ 104 public final AbstractFileChooser getFileChooser() { 105 return fc; 106 } 107 108 /** 109 * Replies the initial directory used to construct the {@code AbstractFileChooser}. 110 * @return The initial directory used to construct the {@code AbstractFileChooser}. 111 */ 112 public final String getInitialDirectory() { 113 return curDir; 114 } 115 116 /** 117 * Creates a new {@link AbstractFileChooser} with default settings. All files will be accepted. 118 * @return this 119 */ 120 public final FileChooserManager createFileChooser() { 121 return doCreateFileChooser(); 122 } 123 124 /** 125 * Creates a new {@link AbstractFileChooser} with given settings for a single {@code FileFilter}. 126 * 127 * @param multiple If true, makes the dialog allow multiple file selections 128 * @param title The string that goes in the dialog window's title bar 129 * @param filter The only file filter that will be proposed by the dialog 130 * @param selectionMode The selection mode that allows the user to:<br><ul> 131 * <li>just select files ({@code JFileChooser.FILES_ONLY})</li> 132 * <li>just select directories ({@code JFileChooser.DIRECTORIES_ONLY})</li> 133 * <li>select both files and directories ({@code JFileChooser.FILES_AND_DIRECTORIES})</li></ul> 134 * @return this 135 * @see DiskAccessAction#createAndOpenFileChooser(boolean, boolean, String, FileFilter, int, String) 136 */ 137 public final FileChooserManager createFileChooser(boolean multiple, String title, FileFilter filter, int selectionMode) { 138 multiple(multiple); 139 title(title); 140 filters(Collections.singleton(filter)); 141 defaultFilter(filter); 142 selectionMode(selectionMode); 143 144 doCreateFileChooser(); 145 fc.setAcceptAllFileFilterUsed(false); 146 return this; 147 } 148 149 /** 150 * Creates a new {@link AbstractFileChooser} with given settings for a collection of {@code FileFilter}s. 151 * 152 * @param multiple If true, makes the dialog allow multiple file selections 153 * @param title The string that goes in the dialog window's title bar 154 * @param filters The file filters that will be proposed by the dialog 155 * @param defaultFilter The file filter that will be selected by default 156 * @param selectionMode The selection mode that allows the user to:<br><ul> 157 * <li>just select files ({@code JFileChooser.FILES_ONLY})</li> 158 * <li>just select directories ({@code JFileChooser.DIRECTORIES_ONLY})</li> 159 * <li>select both files and directories ({@code JFileChooser.FILES_AND_DIRECTORIES})</li></ul> 160 * @return this 161 * @see DiskAccessAction#createAndOpenFileChooser(boolean, boolean, String, Collection, FileFilter, int, String) 162 */ 163 public final FileChooserManager createFileChooser(boolean multiple, String title, Collection<? extends FileFilter> filters, 164 FileFilter defaultFilter, int selectionMode) { 165 multiple(multiple); 166 title(title); 167 filters(filters); 168 defaultFilter(defaultFilter); 169 selectionMode(selectionMode); 170 return doCreateFileChooser(); 171 } 172 173 /** 174 * Creates a new {@link AbstractFileChooser} with given settings for a file extension. 175 * 176 * @param multiple If true, makes the dialog allow multiple file selections 177 * @param title The string that goes in the dialog window's title bar 178 * @param extension The file extension that will be selected as the default file filter 179 * @param allTypes If true, all the files types known by JOSM will be proposed in the "file type" combobox. 180 * If false, only the file filters that include {@code extension} will be proposed 181 * @param selectionMode The selection mode that allows the user to:<br><ul> 182 * <li>just select files ({@code JFileChooser.FILES_ONLY})</li> 183 * <li>just select directories ({@code JFileChooser.DIRECTORIES_ONLY})</li> 184 * <li>select both files and directories ({@code JFileChooser.FILES_AND_DIRECTORIES})</li></ul> 185 * @return this 186 * @see DiskAccessAction#createAndOpenFileChooser(boolean, boolean, String, FileFilter, int, String) 187 */ 188 public final FileChooserManager createFileChooser(boolean multiple, String title, String extension, boolean allTypes, int selectionMode) { 189 multiple(multiple); 190 title(title); 191 extension(extension); 192 allTypes(allTypes); 193 selectionMode(selectionMode); 194 return doCreateFileChooser(); 195 } 196 197 /** 198 * Builder method to set {@code multiple} property. 199 * @param value If true, makes the dialog allow multiple file selections 200 * @return this 201 */ 202 public FileChooserManager multiple(boolean value) { 203 multiple = value; 204 return this; 205 } 206 207 /** 208 * Builder method to set {@code title} property. 209 * @param value The string that goes in the dialog window's title bar 210 * @return this 211 */ 212 public FileChooserManager title(String value) { 213 title = value; 214 return this; 215 } 216 217 /** 218 * Builder method to set {@code filters} property. 219 * @param value The file filters that will be proposed by the dialog 220 * @return this 221 */ 222 public FileChooserManager filters(Collection<? extends FileFilter> value) { 223 filters = value; 224 return this; 225 } 226 227 /** 228 * Builder method to set {@code defaultFilter} property. 229 * @param value The file filter that will be selected by default 230 * @return this 231 */ 232 public FileChooserManager defaultFilter(FileFilter value) { 233 defaultFilter = value; 234 return this; 235 } 236 237 /** 238 * Builder method to set {@code selectionMode} property. 239 * @param value The selection mode that allows the user to:<br><ul> 240 * <li>just select files ({@code JFileChooser.FILES_ONLY})</li> 241 * <li>just select directories ({@code JFileChooser.DIRECTORIES_ONLY})</li> 242 * <li>select both files and directories ({@code JFileChooser.FILES_AND_DIRECTORIES})</li></ul> 243 * @return this 244 */ 245 public FileChooserManager selectionMode(int value) { 246 selectionMode = value; 247 return this; 248 } 249 250 /** 251 * Builder method to set {@code extension} property. 252 * @param value The file extension that will be selected as the default file filter 253 * @return this 254 */ 255 public FileChooserManager extension(String value) { 256 extension = value; 257 return this; 258 } 259 260 /** 261 * Builder method to set {@code allTypes} property. 262 * @param value If true, all the files types known by JOSM will be proposed in the "file type" combobox. 263 * If false, only the file filters that include {@code extension} will be proposed 264 * @return this 265 */ 266 public FileChooserManager allTypes(boolean value) { 267 allTypes = value; 268 return this; 269 } 270 271 /** 272 * Builder method to set {@code file} property. 273 * @param value {@link File} object with default filename 274 * @return this 275 */ 276 public FileChooserManager file(File value) { 277 file = value; 278 return this; 279 } 280 281 /** 282 * Builds {@code FileChooserManager} object using properties set by builder methods or default values. 283 * @return this 284 */ 285 public FileChooserManager doCreateFileChooser() { 286 File f = new File(curDir); 287 // Use native dialog is preference is set, unless an unsupported selection mode is specifically wanted 288 if (PROP_USE_NATIVE_FILE_DIALOG.get() && NativeFileChooser.supportsSelectionMode(selectionMode)) { 289 fc = new NativeFileChooser(f); 290 } else { 291 fc = new SwingFileChooser(f); 292 } 293 294 if (title != null) { 295 fc.setDialogTitle(title); 296 } 297 298 fc.setFileSelectionMode(selectionMode); 299 fc.setMultiSelectionEnabled(multiple); 300 fc.setAcceptAllFileFilterUsed(false); 301 fc.setSelectedFile(this.file); 302 303 if (filters != null) { 304 for (FileFilter filter : filters) { 305 fc.addChoosableFileFilter(filter); 306 } 307 if (defaultFilter != null) { 308 fc.setFileFilter(defaultFilter); 309 } 310 } else if (open) { 311 ExtensionFileFilter.applyChoosableImportFileFilters(fc, extension, allTypes); 312 } else { 313 ExtensionFileFilter.applyChoosableExportFileFilters(fc, extension, allTypes); 314 } 315 return this; 316 } 317 318 /** 319 * Opens the {@code AbstractFileChooser} that has been created. 320 * @return the {@code AbstractFileChooser} if the user effectively choses a file or directory. {@code null} if the user cancelled the dialog. 321 */ 322 public final AbstractFileChooser openFileChooser() { 323 return openFileChooser(null); 324 } 325 326 /** 327 * Opens the {@code AbstractFileChooser} that has been created and waits for the user to choose a file/directory, or cancel the dialog.<br> 328 * When the user choses a file or directory, the {@code lastDirProperty} is updated to the chosen directory path. 329 * 330 * @param parent The Component used as the parent of the AbstractFileChooser. If null, uses {@code Main.parent}. 331 * @return the {@code AbstractFileChooser} if the user effectively choses a file or directory. {@code null} if the user cancelled the dialog. 332 */ 333 public AbstractFileChooser openFileChooser(Component parent) { 334 if (fc == null) 335 doCreateFileChooser(); 336 337 if (parent == null) { 338 parent = Main.parent; 339 } 340 341 int answer = open ? fc.showOpenDialog(parent) : fc.showSaveDialog(parent); 342 if (answer != JFileChooser.APPROVE_OPTION) { 343 return null; 344 } 345 346 if (!fc.getCurrentDirectory().getAbsolutePath().equals(curDir)) { 347 Main.pref.put(lastDirProperty, fc.getCurrentDirectory().getAbsolutePath()); 348 } 349 350 if (!open && !FileChooserManager.PROP_USE_NATIVE_FILE_DIALOG.get() && 351 !SaveActionBase.confirmOverwrite(fc.getSelectedFile())) { 352 return null; 353 } 354 return fc; 355 } 356 357 /** 358 * Opens the file chooser dialog, then checks if filename has the given extension. 359 * If not, adds the extension and asks for overwrite if filename exists. 360 * 361 * @return the {@code File} or {@code null} if the user cancelled the dialog. 362 */ 363 public File getFileForSave() { 364 return SaveActionBase.checkFileAndConfirmOverWrite(openFileChooser(), extension); 365 } 366}