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 AbstractFileChooser fc; 041 042 /** 043 * Creates a new {@code FileChooserManager}. 044 * @param open If true, "Open File" dialogs will be created. If false, "Save File" dialogs will be created. 045 * @see #createFileChooser 046 */ 047 public FileChooserManager(boolean open) { 048 this(open, null); 049 } 050 051 // CHECKSTYLE.OFF: LineLength 052 053 /** 054 * Creates a new {@code FileChooserManager}. 055 * @param open If true, "Open File" dialogs will be created. If false, "Save File" dialogs will be created. 056 * @param lastDirProperty The name of the property used to get the last directory. This directory is used to initialize the AbstractFileChooser. 057 * Then, if the user effectively chooses a file or a directory, this property will be updated to the directory path. 058 * @see #createFileChooser 059 */ 060 public FileChooserManager(boolean open, String lastDirProperty) { 061 this(open, lastDirProperty, null); 062 } 063 064 /** 065 * Creates a new {@code FileChooserManager}. 066 * @param open If true, "Open File" dialogs will be created. If false, "Save File" dialogs will be created. 067 * @param lastDirProperty The name of the property used to get the last directory. This directory is used to initialize the AbstractFileChooser. 068 * Then, if the user effectively chooses a file or a directory, this property will be updated to the directory path. 069 * @param defaultDir The default directory used to initialize the AbstractFileChooser if the {@code lastDirProperty} property value is missing. 070 * @see #createFileChooser 071 */ 072 public FileChooserManager(boolean open, String lastDirProperty, String defaultDir) { 073 this.open = open; 074 this.lastDirProperty = lastDirProperty == null || lastDirProperty.isEmpty() ? "lastDirectory" : lastDirProperty; 075 this.curDir = Main.pref.get(this.lastDirProperty).isEmpty() ? 076 defaultDir == null || defaultDir.isEmpty() ? "." : defaultDir 077 : Main.pref.get(this.lastDirProperty); 078 } 079 080 // CHECKSTYLE.ON: LineLength 081 082 /** 083 * Replies the {@code AbstractFileChooser} that has been previously created. 084 * @return The {@code AbstractFileChooser} that has been previously created, or {@code null} if it has not been created yet. 085 * @see #createFileChooser 086 */ 087 public final AbstractFileChooser getFileChooser() { 088 return fc; 089 } 090 091 /** 092 * Replies the initial directory used to construct the {@code AbstractFileChooser}. 093 * @return The initial directory used to construct the {@code AbstractFileChooser}. 094 */ 095 public final String getInitialDirectory() { 096 return curDir; 097 } 098 099 /** 100 * Creates a new {@link AbstractFileChooser} with default settings. All files will be accepted. 101 * @return this 102 */ 103 public final FileChooserManager createFileChooser() { 104 return doCreateFileChooser(false, null, null, null, null, JFileChooser.FILES_ONLY, false); 105 } 106 107 /** 108 * Creates a new {@link AbstractFileChooser} with given settings for a single {@code FileFilter}. 109 * 110 * @param multiple If true, makes the dialog allow multiple file selections 111 * @param title The string that goes in the dialog window's title bar 112 * @param filter The only file filter that will be proposed by the dialog 113 * @param selectionMode The selection mode that allows the user to:<br><ul> 114 * <li>just select files ({@code JFileChooser.FILES_ONLY})</li> 115 * <li>just select directories ({@code JFileChooser.DIRECTORIES_ONLY})</li> 116 * <li>select both files and directories ({@code JFileChooser.FILES_AND_DIRECTORIES})</li></ul> 117 * @return this 118 * @see DiskAccessAction#createAndOpenFileChooser(boolean, boolean, String, FileFilter, int, String) 119 */ 120 public final FileChooserManager createFileChooser(boolean multiple, String title, FileFilter filter, int selectionMode) { 121 doCreateFileChooser(multiple, title, Collections.singleton(filter), filter, null, selectionMode, false); 122 getFileChooser().setAcceptAllFileFilterUsed(false); 123 return this; 124 } 125 126 /** 127 * Creates a new {@link AbstractFileChooser} with given settings for a collection of {@code FileFilter}s. 128 * 129 * @param multiple If true, makes the dialog allow multiple file selections 130 * @param title The string that goes in the dialog window's title bar 131 * @param filters The file filters that will be proposed by the dialog 132 * @param defaultFilter The file filter that will be selected by default 133 * @param selectionMode The selection mode that allows the user to:<br><ul> 134 * <li>just select files ({@code JFileChooser.FILES_ONLY})</li> 135 * <li>just select directories ({@code JFileChooser.DIRECTORIES_ONLY})</li> 136 * <li>select both files and directories ({@code JFileChooser.FILES_AND_DIRECTORIES})</li></ul> 137 * @return this 138 * @see DiskAccessAction#createAndOpenFileChooser(boolean, boolean, String, Collection, FileFilter, int, String) 139 */ 140 public final FileChooserManager createFileChooser(boolean multiple, String title, Collection<? extends FileFilter> filters, 141 FileFilter defaultFilter, int selectionMode) { 142 return doCreateFileChooser(multiple, title, filters, defaultFilter, null, selectionMode, false); 143 } 144 145 /** 146 * Creates a new {@link AbstractFileChooser} with given settings for a file extension. 147 * 148 * @param multiple If true, makes the dialog allow multiple file selections 149 * @param title The string that goes in the dialog window's title bar 150 * @param extension The file extension that will be selected as the default file filter 151 * @param allTypes If true, all the files types known by JOSM will be proposed in the "file type" combobox. 152 * If false, only the file filters that include {@code extension} will be proposed 153 * @param selectionMode The selection mode that allows the user to:<br><ul> 154 * <li>just select files ({@code JFileChooser.FILES_ONLY})</li> 155 * <li>just select directories ({@code JFileChooser.DIRECTORIES_ONLY})</li> 156 * <li>select both files and directories ({@code JFileChooser.FILES_AND_DIRECTORIES})</li></ul> 157 * @return this 158 * @see DiskAccessAction#createAndOpenFileChooser(boolean, boolean, String, FileFilter, int, String) 159 */ 160 public final FileChooserManager createFileChooser(boolean multiple, String title, String extension, boolean allTypes, int selectionMode) { 161 return doCreateFileChooser(multiple, title, null, null, extension, selectionMode, allTypes); 162 } 163 164 private FileChooserManager doCreateFileChooser(boolean multiple, String title, Collection<? extends FileFilter> filters, 165 FileFilter defaultFilter, String extension, int selectionMode, boolean allTypes) { 166 File file = new File(curDir); 167 // Use native dialog is preference is set, unless an unsupported selection mode is specifically wanted 168 if (PROP_USE_NATIVE_FILE_DIALOG.get() && NativeFileChooser.supportsSelectionMode(selectionMode)) { 169 fc = new NativeFileChooser(file); 170 } else { 171 fc = new SwingFileChooser(file); 172 } 173 174 if (title != null) { 175 fc.setDialogTitle(title); 176 } 177 178 fc.setFileSelectionMode(selectionMode); 179 fc.setMultiSelectionEnabled(multiple); 180 fc.setAcceptAllFileFilterUsed(false); 181 182 if (filters != null) { 183 for (FileFilter filter : filters) { 184 fc.addChoosableFileFilter(filter); 185 } 186 if (defaultFilter != null) { 187 fc.setFileFilter(defaultFilter); 188 } 189 } else if (open) { 190 ExtensionFileFilter.applyChoosableImportFileFilters(fc, extension, allTypes); 191 } else { 192 ExtensionFileFilter.applyChoosableExportFileFilters(fc, extension, allTypes); 193 } 194 return this; 195 } 196 197 /** 198 * Opens the {@code AbstractFileChooser} that has been created. Nothing happens if it has not been created yet. 199 * @return the {@code AbstractFileChooser} if the user effectively choses a file or directory. {@code null} if the user cancelled the dialog. 200 */ 201 public final AbstractFileChooser openFileChooser() { 202 return openFileChooser(null); 203 } 204 205 /** 206 * Opens the {@code AbstractFileChooser} that has been created and waits for the user to choose a file/directory, or cancel the dialog.<br> 207 * Nothing happens if the dialog has not been created yet.<br> 208 * When the user choses a file or directory, the {@code lastDirProperty} is updated to the chosen directory path. 209 * 210 * @param parent The Component used as the parent of the AbstractFileChooser. If null, uses {@code Main.parent}. 211 * @return the {@code AbstractFileChooser} if the user effectively choses a file or directory. {@code null} if the user cancelled the dialog. 212 */ 213 public AbstractFileChooser openFileChooser(Component parent) { 214 if (fc != null) { 215 if (parent == null) { 216 parent = Main.parent; 217 } 218 219 int answer = open ? fc.showOpenDialog(parent) : fc.showSaveDialog(parent); 220 if (answer != JFileChooser.APPROVE_OPTION) { 221 return null; 222 } 223 224 if (!fc.getCurrentDirectory().getAbsolutePath().equals(curDir)) { 225 Main.pref.put(lastDirProperty, fc.getCurrentDirectory().getAbsolutePath()); 226 } 227 228 if (!open) { 229 File file = fc.getSelectedFile(); 230 if (!SaveActionBase.confirmOverwrite(file)) { 231 return null; 232 } 233 } 234 } 235 return fc; 236 } 237}