// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.gui.widgets;
import java.awt.Component;
import java.io.File;
import java.util.Collection;
import java.util.Collections;
import javax.swing.JFileChooser;
import javax.swing.filechooser.FileFilter;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.actions.DiskAccessAction;
import org.openstreetmap.josm.actions.ExtensionFileFilter;
import org.openstreetmap.josm.actions.SaveActionBase;
import org.openstreetmap.josm.data.preferences.BooleanProperty;
/**
* A chained utility class used to create and open {@link AbstractFileChooser} dialogs.<br>
* Use only this class if you need to control specifically your AbstractFileChooser dialog.<br>
* <p>
* A simpler usage is to call the {@link DiskAccessAction#createAndOpenFileChooser} methods.
*
* @since 5438 (creation)
* @since 7578 (rename)
*/
public class FileChooserManager {
/**
* Property to enable use of native file dialogs.
*/
public static final BooleanProperty PROP_USE_NATIVE_FILE_DIALOG = new BooleanProperty("use.native.file.dialog",
// Native dialogs do not support file filters, so do not set them as default, except for OS X where they never worked
Main.isPlatformOsx());
private final boolean open;
private final String lastDirProperty;
private final String curDir;
private boolean multiple;
private String title;
private Collection<? extends FileFilter> filters;
private FileFilter defaultFilter;
private int selectionMode = JFileChooser.FILES_ONLY;
private String extension;
private boolean allTypes;
private File file;
private AbstractFileChooser fc;
/**
* Creates a new {@code FileChooserManager} with default values.
* @see #createFileChooser
*/
public FileChooserManager() {
this(false, null, null);
}
/**
* Creates a new {@code FileChooserManager}.
* @param open If true, "Open File" dialogs will be created. If false, "Save File" dialogs will be created.
* @see #createFileChooser
*/
public FileChooserManager(boolean open) {
this(open, null);
}
// CHECKSTYLE.OFF: LineLength
/**
* Creates a new {@code FileChooserManager}.
* @param open If true, "Open File" dialogs will be created. If false, "Save File" dialogs will be created.
* @param lastDirProperty The name of the property used to get the last directory. This directory is used to initialize the AbstractFileChooser.
* Then, if the user effectively chooses a file or a directory, this property will be updated to the directory path.
* @see #createFileChooser
*/
public FileChooserManager(boolean open, String lastDirProperty) {
this(open, lastDirProperty, null);
}
/**
* Creates a new {@code FileChooserManager}.
* @param open If true, "Open File" dialogs will be created. If false, "Save File" dialogs will be created.
* @param lastDirProperty The name of the property used to get the last directory. This directory is used to initialize the AbstractFileChooser.
* Then, if the user effectively chooses a file or a directory, this property will be updated to the directory path.
* @param defaultDir The default directory used to initialize the AbstractFileChooser if the {@code lastDirProperty} property value is missing.
* @see #createFileChooser
*/
public FileChooserManager(boolean open, String lastDirProperty, String defaultDir) {
this.open = open;
this.lastDirProperty = lastDirProperty == null || lastDirProperty.isEmpty() ? "lastDirectory" : lastDirProperty;
this.curDir = Main.pref.get(this.lastDirProperty).isEmpty() ?
defaultDir == null || defaultDir.isEmpty() ? "." : defaultDir
: Main.pref.get(this.lastDirProperty);
}
// CHECKSTYLE.ON: LineLength
/**
* Replies the {@code AbstractFileChooser} that has been previously created.
* @return The {@code AbstractFileChooser} that has been previously created, or {@code null} if it has not been created yet.
* @see #createFileChooser
*/
public final AbstractFileChooser getFileChooser() {
return fc;
}
/**
* Replies the initial directory used to construct the {@code AbstractFileChooser}.
* @return The initial directory used to construct the {@code AbstractFileChooser}.
*/
public final String getInitialDirectory() {
return curDir;
}
/**
* Creates a new {@link AbstractFileChooser} with default settings. All files will be accepted.
* @return this
*/
public final FileChooserManager createFileChooser() {
return doCreateFileChooser();
}
/**
* Creates a new {@link AbstractFileChooser} with given settings for a single {@code FileFilter}.
*
* @param multiple If true, makes the dialog allow multiple file selections
* @param title The string that goes in the dialog window's title bar
* @param filter The only file filter that will be proposed by the dialog
* @param selectionMode The selection mode that allows the user to:<br><ul>
* <li>just select files ({@code JFileChooser.FILES_ONLY})</li>
* <li>just select directories ({@code JFileChooser.DIRECTORIES_ONLY})</li>
* <li>select both files and directories ({@code JFileChooser.FILES_AND_DIRECTORIES})</li></ul>
* @return this
* @see DiskAccessAction#createAndOpenFileChooser(boolean, boolean, String, FileFilter, int, String)
*/
public final FileChooserManager createFileChooser(boolean multiple, String title, FileFilter filter, int selectionMode) {
multiple(multiple);
title(title);
filters(Collections.singleton(filter));
defaultFilter(filter);
selectionMode(selectionMode);
doCreateFileChooser();
fc.setAcceptAllFileFilterUsed(false);
return this;
}
/**
* Creates a new {@link AbstractFileChooser} with given settings for a collection of {@code FileFilter}s.
*
* @param multiple If true, makes the dialog allow multiple file selections
* @param title The string that goes in the dialog window's title bar
* @param filters The file filters that will be proposed by the dialog
* @param defaultFilter The file filter that will be selected by default
* @param selectionMode The selection mode that allows the user to:<br><ul>
* <li>just select files ({@code JFileChooser.FILES_ONLY})</li>
* <li>just select directories ({@code JFileChooser.DIRECTORIES_ONLY})</li>
* <li>select both files and directories ({@code JFileChooser.FILES_AND_DIRECTORIES})</li></ul>
* @return this
* @see DiskAccessAction#createAndOpenFileChooser(boolean, boolean, String, Collection, FileFilter, int, String)
*/
public final FileChooserManager createFileChooser(boolean multiple, String title, Collection<? extends FileFilter> filters,
FileFilter defaultFilter, int selectionMode) {
multiple(multiple);
title(title);
filters(filters);
defaultFilter(defaultFilter);
selectionMode(selectionMode);
return doCreateFileChooser();
}
/**
* Creates a new {@link AbstractFileChooser} with given settings for a file extension.
*
* @param multiple If true, makes the dialog allow multiple file selections
* @param title The string that goes in the dialog window's title bar
* @param extension The file extension that will be selected as the default file filter
* @param allTypes If true, all the files types known by JOSM will be proposed in the "file type" combobox.
* If false, only the file filters that include {@code extension} will be proposed
* @param selectionMode The selection mode that allows the user to:<br><ul>
* <li>just select files ({@code JFileChooser.FILES_ONLY})</li>
* <li>just select directories ({@code JFileChooser.DIRECTORIES_ONLY})</li>
* <li>select both files and directories ({@code JFileChooser.FILES_AND_DIRECTORIES})</li></ul>
* @return this
* @see DiskAccessAction#createAndOpenFileChooser(boolean, boolean, String, FileFilter, int, String)
*/
public final FileChooserManager createFileChooser(boolean multiple, String title, String extension, boolean allTypes, int selectionMode) {
multiple(multiple);
title(title);
extension(extension);
allTypes(allTypes);
selectionMode(selectionMode);
return doCreateFileChooser();
}
/**
* Builder method to set {@code multiple} property.
* @param value If true, makes the dialog allow multiple file selections
* @return this
*/
public FileChooserManager multiple(boolean value) {
multiple = value;
return this;
}
/**
* Builder method to set {@code title} property.
* @param value The string that goes in the dialog window's title bar
* @return this
*/
public FileChooserManager title(String value) {
title = value;
return this;
}
/**
* Builder method to set {@code filters} property.
* @param value The file filters that will be proposed by the dialog
* @return this
*/
public FileChooserManager filters(Collection<? extends FileFilter> value) {
filters = value;
return this;
}
/**
* Builder method to set {@code defaultFilter} property.
* @param value The file filter that will be selected by default
* @return this
*/
public FileChooserManager defaultFilter(FileFilter value) {
defaultFilter = value;
return this;
}
/**
* Builder method to set {@code selectionMode} property.
* @param value The selection mode that allows the user to:<br><ul>
* <li>just select files ({@code JFileChooser.FILES_ONLY})</li>
* <li>just select directories ({@code JFileChooser.DIRECTORIES_ONLY})</li>
* <li>select both files and directories ({@code JFileChooser.FILES_AND_DIRECTORIES})</li></ul>
* @return this
*/
public FileChooserManager selectionMode(int value) {
selectionMode = value;
return this;
}
/**
* Builder method to set {@code extension} property.
* @param value The file extension that will be selected as the default file filter
* @return this
*/
public FileChooserManager extension(String value) {
extension = value;
return this;
}
/**
* Builder method to set {@code allTypes} property.
* @param value If true, all the files types known by JOSM will be proposed in the "file type" combobox.
* If false, only the file filters that include {@code extension} will be proposed
* @return this
*/
public FileChooserManager allTypes(boolean value) {
allTypes = value;
return this;
}
/**
* Builder method to set {@code file} property.
* @param value {@link File} object with default filename
* @return this
*/
public FileChooserManager file(File value) {
file = value;
return this;
}
/**
* Builds {@code FileChooserManager} object using properties set by builder methods or default values.
* @return this
*/
public FileChooserManager doCreateFileChooser() {
File f = new File(curDir);
// Use native dialog is preference is set, unless an unsupported selection mode is specifically wanted
if (PROP_USE_NATIVE_FILE_DIALOG.get() && NativeFileChooser.supportsSelectionMode(selectionMode)) {
fc = new NativeFileChooser(f);
} else {
fc = new SwingFileChooser(f);
}
if (title != null) {
fc.setDialogTitle(title);
}
fc.setFileSelectionMode(selectionMode);
fc.setMultiSelectionEnabled(multiple);
fc.setAcceptAllFileFilterUsed(false);
fc.setSelectedFile(this.file);
if (filters != null) {
for (FileFilter filter : filters) {
fc.addChoosableFileFilter(filter);
}
if (defaultFilter != null) {
fc.setFileFilter(defaultFilter);
}
} else if (open) {
ExtensionFileFilter.applyChoosableImportFileFilters(fc, extension, allTypes);
} else {
ExtensionFileFilter.applyChoosableExportFileFilters(fc, extension, allTypes);
}
return this;
}
/**
* Opens the {@code AbstractFileChooser} that has been created.
* @return the {@code AbstractFileChooser} if the user effectively choses a file or directory. {@code null} if the user cancelled the dialog.
*/
public final AbstractFileChooser openFileChooser() {
return openFileChooser(null);
}
/**
* Opens the {@code AbstractFileChooser} that has been created and waits for the user to choose a file/directory, or cancel the dialog.<br>
* When the user choses a file or directory, the {@code lastDirProperty} is updated to the chosen directory path.
*
* @param parent The Component used as the parent of the AbstractFileChooser. If null, uses {@code Main.parent}.
* @return the {@code AbstractFileChooser} if the user effectively choses a file or directory. {@code null} if the user cancelled the dialog.
*/
public AbstractFileChooser openFileChooser(Component parent) {
if (fc == null)
doCreateFileChooser();
if (parent == null) {
parent = Main.parent;
}
int answer = open ? fc.showOpenDialog(parent) : fc.showSaveDialog(parent);
if (answer != JFileChooser.APPROVE_OPTION) {
return null;
}
if (!fc.getCurrentDirectory().getAbsolutePath().equals(curDir)) {
Main.pref.put(lastDirProperty, fc.getCurrentDirectory().getAbsolutePath());
}
if (!open && !FileChooserManager.PROP_USE_NATIVE_FILE_DIALOG.get() &&
!SaveActionBase.confirmOverwrite(fc.getSelectedFile())) {
return null;
}
return fc;
}
/**
* Opens the file chooser dialog, then checks if filename has the given extension.
* If not, adds the extension and asks for overwrite if filename exists.
*
* @return the {@code File} or {@code null} if the user cancelled the dialog.
*/
public File getFileForSave() {
return SaveActionBase.checkFileAndConfirmOverWrite(openFileChooser(), extension);
}
}