diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/SystemFileChooser.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/SystemFileChooser.java index ae08f63f..39fa5a39 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/SystemFileChooser.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/SystemFileChooser.java @@ -23,7 +23,9 @@ import java.awt.Window; import java.io.File; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.Locale; +import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import javax.swing.JDialog; import javax.swing.JFileChooser; @@ -63,10 +65,10 @@ import com.formdev.flatlaf.ui.FlatNativeWindowsLibrary; *
  • Open File and Select Folder dialogs always warn about not existing files/folders. * The operating system shows a warning dialog to inform the user. * It is not possible to customize that warning dialog. - * The file chooser stays open. + * The file dialog stays open. *
  • Save File dialog always asks whether an existing file should be overwritten. * The operating system shows a question dialog to ask the user whether he wants to overwrite the file or not. - * If user selects "Yes", the file chooser closes. If user selects "No", the file chooser stays open. + * If user selects "Yes", the file dialog closes. If user selects "No", the file dialog stays open. * It is not possible to customize that question dialog. *
  • Save File dialog does not support multi-selection. *
  • For selection mode {@link #DIRECTORIES_ONLY}, dialog type {@link #SAVE_DIALOG} is ignored. @@ -83,6 +85,9 @@ import com.formdev.flatlaf.ui.FlatNativeWindowsLibrary; * Use {@code chooser.addChoosableFileFilter( chooser.getAcceptAllFileFilter() )} * to place All Files filter somewhere else. *
  • Accessory components are not supported. + *
  • macOS: By default, the user can not navigate into file packages (e.g. applications). + * If needed, this can be enabled by setting platform property + * {@link #MAC_TREATS_FILE_PACKAGES_AS_DIRECTORIES} to {@code true}. * * * @author Karl Tauber @@ -136,6 +141,112 @@ public class SystemFileChooser private ApproveCallback approveCallback; private int approveResult = APPROVE_OPTION; + + /** + * Windows: Text displayed in front of the filename text field. + * Value type must be {@link String}. + * @see #putPlatformProperty(String, Object) + */ + public static final String WINDOWS_FILE_NAME_LABEL = "windows.fileNameLabel"; + + /** + * Windows: Folder used as a default if there is not a recently used folder value available. + * Windows somewhere stores default folder on a per-app basis. + * So this is probably used only once when the app opens a file dialog for first time. + * Value type must be {@link String}. + * @see #putPlatformProperty(String, Object) + */ + public static final String WINDOWS_DEFAULT_FOLDER = "windows.defaultFolder"; + + /** + * Windows: Default extension to be added to file name in save dialog. + * Value type must be {@link String}. + * @see #putPlatformProperty(String, Object) + */ + public static final String WINDOWS_DEFAULT_EXTENSION = "windows.defaultExtension"; + + /** + * macOS: Text displayed at top of open/save dialogs. + * Value type must be {@link String}. + * @see #putPlatformProperty(String, Object) + */ + public static final String MAC_MESSAGE = "mac.message"; + + /** + * macOS: Text displayed in front of the filter combobox. + * Value type must be {@link String}. + * @see #putPlatformProperty(String, Object) + */ + public static final String MAC_FILTER_FIELD_LABEL = "mac.filterFieldLabel"; + + /** + * macOS: Text displayed in front of the filename text field in save dialog (not used in open dialog). + * Value type must be {@link String}. + * @see #putPlatformProperty(String, Object) + */ + public static final String MAC_NAME_FIELD_LABEL = "mac.nameFieldLabel"; + + /** + * macOS: If {@code true}, displays file packages (e.g. applications) as directories + * and allows the user to navigate into the file package. + * Value type must be {@link Boolean}. + * @see #putPlatformProperty(String, Object) + */ + public static final String MAC_TREATS_FILE_PACKAGES_AS_DIRECTORIES = "mac.treatsFilePackagesAsDirectories"; + + /** + * Windows: Low-level options to set. See {@code FOS_*} constants in {@link FlatNativeWindowsLibrary}. + * Options {@code FOS_PICKFOLDERS}, {@code FOS_ALLOWMULTISELECT} and {@code FOS_FORCESHOWHIDDEN} can not be modified. + * Value type must be {@link Integer}. + * @see #putPlatformProperty(String, Object) + */ + public static final String WINDOWS_OPTIONS_SET = "windows.optionsSet"; + + /** + * Windows: Low-level options to clear. See {@code FOS_*} constants in {@link FlatNativeWindowsLibrary}. + * Options {@code FOS_PICKFOLDERS}, {@code FOS_ALLOWMULTISELECT} and {@code FOS_FORCESHOWHIDDEN} can not be modified. + * Value type must be {@link Integer}. + * @see #putPlatformProperty(String, Object) + */ + public static final String WINDOWS_OPTIONS_CLEAR = "windows.optionsClear"; + + /** + * macOS: Low-level options to set. See {@code FC_*} constants in {@link FlatNativeMacLibrary}. + * Options {@code FC_canChooseFiles}, {@code FC_canChooseDirectories}, + * {@code FC_allowsMultipleSelection} and {@code FC_showsHiddenFiles} can not be modified. + * Value type must be {@link Integer}. + * @see #putPlatformProperty(String, Object) + */ + public static final String MAC_OPTIONS_SET = "mac.optionsSet"; + + /** + * macOS: Low-level options to clear. See {@code FC_*} constants in {@link FlatNativeMacLibrary}. + * Options {@code FC_canChooseFiles}, {@code FC_canChooseDirectories}, + * {@code FC_allowsMultipleSelection} and {@code FC_showsHiddenFiles} can not be modified. + * Value type must be {@link Integer}. + * @see #putPlatformProperty(String, Object) + */ + public static final String MAC_OPTIONS_CLEAR = "mac.optionsClear"; + + /** + * Linux: Low-level options to set. See {@code FC_*} constants in {@link FlatNativeLinuxLibrary}. + * Options {@code FC_select_folder}, {@code FC_select_multiple} and {@code FC_show_hidden} can not be modified. + * Value type must be {@link Integer}. + * @see #putPlatformProperty(String, Object) + */ + public static final String LINUX_OPTIONS_SET = "linux.optionsSet"; + + /** + * Linux: Low-level options to clear. See {@code FC_*} constants in {@link FlatNativeLinuxLibrary}. + * Options {@code FC_select_folder}, {@code FC_select_multiple} and {@code FC_show_hidden} can not be modified. + * Value type must be {@link Integer}. + * @see #putPlatformProperty(String, Object) + */ + public static final String LINUX_OPTIONS_CLEAR = "linux.optionsClear"; + + private Map platformProperties; + + /** @see JFileChooser#JFileChooser() */ public SystemFileChooser() { this( (File) null ); @@ -472,6 +583,38 @@ public class SystemFileChooser this.approveCallback = approveCallback; } + @SuppressWarnings( "unchecked" ) + public T getPlatformProperty( String key ) { + return (platformProperties != null) ? (T) platformProperties.get( key ) : null; + } + + /** + * Set a platform specific file dialog property. + *

    + * For supported properties see {@code WINDOWS_}, {@code MAC_} and {@code LINUX_} constants in this class. + * + *

    {@code
    +	 * chooser.putPlatformProperty( SystemFileChooser.WINDOWS_FILE_NAME_LABEL, "My filename label:" );
    +	 * chooser.putPlatformProperty( SystemFileChooser.MAC_TREATS_FILE_PACKAGES_AS_DIRECTORIES, true );
    +	 * chooser.putPlatformProperty( SystemFileChooser.LINUX_OPTIONS_CLEAR,
    +	 *     FlatNativeLinuxLibrary.FC_create_folders | FlatNativeLinuxLibrary.FC_do_overwrite_confirmation );
    +	 * }
    + */ + public void putPlatformProperty( String key, Object value ) { + if( platformProperties == null ) + platformProperties = new HashMap<>(); + + if( value != null ) + platformProperties.put( key, value ); + else + platformProperties.remove( key ); + } + + private int getPlatformOptions( String key, int optionsBlocked ) { + Object value = getPlatformProperty( key ); + return (value instanceof Integer) ? (Integer) value & ~optionsBlocked : 0; + } + private int showDialogImpl( Component parent ) { approveResult = APPROVE_OPTION; File[] files = getProvider().showDialog( parent, this ); @@ -597,8 +740,13 @@ public class SystemFileChooser folder = currentDirectory.getAbsolutePath(); // options - int optionsSet = FlatNativeWindowsLibrary.FOS_OVERWRITEPROMPT; - int optionsClear = 0; + int optionsBlocked = FlatNativeWindowsLibrary.FOS_PICKFOLDERS + | FlatNativeWindowsLibrary.FOS_ALLOWMULTISELECT + | FlatNativeWindowsLibrary.FOS_FORCESHOWHIDDEN; + int optionsSet = fc.getPlatformOptions( WINDOWS_OPTIONS_SET, optionsBlocked ); + int optionsClear = fc.getPlatformOptions( WINDOWS_OPTIONS_CLEAR, optionsBlocked ); + if( (optionsClear & FlatNativeWindowsLibrary.FOS_OVERWRITEPROMPT) == 0 ) + optionsSet |= FlatNativeWindowsLibrary.FOS_OVERWRITEPROMPT; if( fc.isDirectorySelectionEnabled() ) optionsSet |= FlatNativeWindowsLibrary.FOS_PICKFOLDERS; if( fc.isMultiSelectionEnabled() ) @@ -640,8 +788,12 @@ public class SystemFileChooser // show system file dialog return FlatNativeWindowsLibrary.showFileChooser( owner, open, - fc.getDialogTitle(), approveButtonText, null, fileName, - folder, saveAsItem, null, null, optionsSet, optionsClear, callback, + fc.getDialogTitle(), approveButtonText, + fc.getPlatformProperty( WINDOWS_FILE_NAME_LABEL ), + fileName, folder, saveAsItem, + fc.getPlatformProperty( WINDOWS_DEFAULT_FOLDER ), + fc.getPlatformProperty( WINDOWS_DEFAULT_EXTENSION ), + optionsSet, optionsClear, callback, fileTypeIndex, fileTypes.toArray( new String[fileTypes.size()] ) ); } @@ -703,8 +855,14 @@ public class SystemFileChooser directoryURL = currentDirectory.getAbsolutePath(); // options - int optionsSet = FlatNativeMacLibrary.FC_accessoryViewDisclosed; - int optionsClear = 0; + int optionsBlocked = FlatNativeMacLibrary.FC_canChooseFiles + | FlatNativeMacLibrary.FC_canChooseDirectories + | FlatNativeMacLibrary.FC_allowsMultipleSelection + | FlatNativeMacLibrary.FC_showsHiddenFiles; + int optionsSet = fc.getPlatformOptions( MAC_OPTIONS_SET, optionsBlocked ); + int optionsClear = fc.getPlatformOptions( MAC_OPTIONS_CLEAR, optionsBlocked ); + if( (optionsClear & FlatNativeMacLibrary.FC_accessoryViewDisclosed) == 0 ) + optionsSet |= FlatNativeMacLibrary.FC_accessoryViewDisclosed; if( fc.isDirectorySelectionEnabled() ) { optionsSet |= FlatNativeMacLibrary.FC_canChooseDirectories; optionsClear |= FlatNativeMacLibrary.FC_canChooseFiles; @@ -714,6 +872,8 @@ public class SystemFileChooser optionsSet |= FlatNativeMacLibrary.FC_allowsMultipleSelection; if( !fc.isFileHidingEnabled() ) optionsSet |= FlatNativeMacLibrary.FC_showsHiddenFiles; + if( Boolean.TRUE.equals( fc.getPlatformProperty( MAC_TREATS_FILE_PACKAGES_AS_DIRECTORIES ) ) ) + optionsSet |= FlatNativeMacLibrary.FC_treatsFilePackagesAsDirectories; // filter int fileTypeIndex = 0; @@ -742,7 +902,10 @@ public class SystemFileChooser // show system file dialog return FlatNativeMacLibrary.showFileChooser( open, - fc.getDialogTitle(), fc.getApproveButtonText(), null, null, null, + fc.getDialogTitle(), fc.getApproveButtonText(), + fc.getPlatformProperty( MAC_MESSAGE ), + fc.getPlatformProperty( MAC_FILTER_FIELD_LABEL ), + fc.getPlatformProperty( MAC_NAME_FIELD_LABEL ), nameFieldStringValue, directoryURL, optionsSet, optionsClear, callback, fileTypeIndex, fileTypes.toArray( new String[fileTypes.size()] ) ); } @@ -811,8 +974,13 @@ public class SystemFileChooser currentFolder = currentDirectory.getAbsolutePath(); // options - int optionsSet = FlatNativeLinuxLibrary.FC_do_overwrite_confirmation; - int optionsClear = 0; + int optionsBlocked = FlatNativeLinuxLibrary.FC_select_folder + | FlatNativeLinuxLibrary.FC_select_multiple + | FlatNativeLinuxLibrary.FC_show_hidden; + int optionsSet = fc.getPlatformOptions( LINUX_OPTIONS_SET, optionsBlocked ); + int optionsClear = fc.getPlatformOptions( LINUX_OPTIONS_CLEAR, optionsBlocked ); + if( (optionsClear & FlatNativeLinuxLibrary.FC_do_overwrite_confirmation) == 0 ) + optionsSet |= FlatNativeLinuxLibrary.FC_do_overwrite_confirmation; if( fc.isDirectorySelectionEnabled() ) optionsSet |= FlatNativeLinuxLibrary.FC_select_folder; if( fc.isMultiSelectionEnabled() ) @@ -1142,7 +1310,7 @@ public class SystemFileChooser public static abstract class ApproveContext { /** - * Shows a modal (operating system) message dialog as child of the system file chooser. + * Shows a modal (operating system) message dialog as child of the system file dialog. *

    * Use this instead of {@link JOptionPane} in approve callbacks. * diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserTest.java index 6d2102a6..1a2d629e 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserTest.java @@ -202,6 +202,17 @@ public class FlatSystemFileChooserTest SystemFileChooser.FileFilter[] filters = fc.getChoosableFileFilters(); if( filters.length > 0 ) fc.setFileFilter( filters[Math.min( Math.max( fileTypeIndex, 0 ), filters.length - 1 )] ); + +// fc.putPlatformProperty( SystemFileChooser.WINDOWS_FILE_NAME_LABEL, "My filename label:" ); +// fc.putPlatformProperty( SystemFileChooser.WINDOWS_OPTIONS_SET, FlatNativeWindowsLibrary.FOS_HIDEMRUPLACES ); + +// fc.putPlatformProperty( SystemFileChooser.MAC_MESSAGE, "some message" ); +// fc.putPlatformProperty( SystemFileChooser.MAC_NAME_FIELD_LABEL, "My name label:" ); +// fc.putPlatformProperty( SystemFileChooser.MAC_FILTER_FIELD_LABEL, "My filter label" ); +// fc.putPlatformProperty( SystemFileChooser.MAC_TREATS_FILE_PACKAGES_AS_DIRECTORIES, true ); +// fc.putPlatformProperty( SystemFileChooser.MAC_OPTIONS_CLEAR, FlatNativeMacLibrary.FC_showsTagField ); + +// fc.putPlatformProperty( SystemFileChooser.LINUX_OPTIONS_CLEAR, FlatNativeLinuxLibrary.FC_create_folders | FlatNativeLinuxLibrary.FC_do_overwrite_confirmation ); } private void configureSwingFileChooser( JFileChooser fc ) {