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