diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatSystemProperties.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatSystemProperties.java
index db7ccb13..1d333eaa 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatSystemProperties.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatSystemProperties.java
@@ -16,7 +16,9 @@
package com.formdev.flatlaf;
+import javax.swing.JFileChooser;
import javax.swing.SwingUtilities;
+import com.formdev.flatlaf.util.SystemFileChooser;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -226,6 +228,17 @@ public interface FlatSystemProperties
*/
String USE_SUB_MENU_SAFE_TRIANGLE = "flatlaf.useSubMenuSafeTriangle";
+ /**
+ * Specifies whether {@link SystemFileChooser} uses operating system file dialogs.
+ * If set to {@code false}, the {@link JFileChooser} is used instead.
+ *
+ * Allowed Values {@code false} and {@code true}
+ * Default {@code true}
+ *
+ * @since 3.6
+ */
+ String USE_SYSTEM_FILE_CHOOSER = "flatlaf.useSystemFileChooser";
+
/**
* Checks whether a system property is set and returns {@code true} if its value
* is {@code "true"} (case-insensitive), otherwise it returns {@code false}.
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeLinuxLibrary.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeLinuxLibrary.java
index 9ebdda20..f1bed502 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeLinuxLibrary.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeLinuxLibrary.java
@@ -134,6 +134,10 @@ public class FlatNativeLinuxLibrary
* Shows the Linux system file dialog
* GtkFileChooserDialog.
*
+ * Uses {@code GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER} if {@link #FC_select_folder} is set in parameter {@code optionsSet}.
+ * Otherwise uses {@code GTK_FILE_CHOOSER_ACTION_OPEN} if parameter {@code open} is {@code true},
+ * or {@code GTK_FILE_CHOOSER_ACTION_SAVE} if {@code false}.
+ *
* Note: This method blocks the current thread until the user closes
* the file dialog. It is highly recommended to invoke it from a new thread
* to avoid blocking the AWT event dispatching thread.
@@ -142,6 +146,7 @@ public class FlatNativeLinuxLibrary
* @param open if {@code true}, shows the open dialog; if {@code false}, shows the save dialog
* @param title text displayed in dialog title; or {@code null}
* @param okButtonLabel text displayed in default button; or {@code null}. Use '_' for mnemonics (e.g. "_Choose")
+ * Use '__' for '_' character (e.g. "Choose__and__Quit").
* @param currentName user-editable filename currently shown in the filename field in save dialog; or {@code null}
* @param currentFolder current directory shown in the dialog; or {@code null}
* @param optionsSet options to set; see {@code FOS_*} constants
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowsLibrary.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowsLibrary.java
index 3e7c7b0c..f0a769aa 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowsLibrary.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowsLibrary.java
@@ -204,7 +204,8 @@ public class FlatNativeWindowsLibrary
* @param owner the owner of the file dialog; or {@code null}
* @param open if {@code true}, shows the open dialog; if {@code false}, shows the save dialog
* @param title text displayed in dialog title; or {@code null}
- * @param okButtonLabel text displayed in default button; or {@code null}. Use '&' for mnemonics (e.g. "&Choose")
+ * @param okButtonLabel text displayed in default button; or {@code null}. Use '&' for mnemonics (e.g. "&Choose").
+ * Use '&&' for '&' character (e.g. "Choose && Quit").
* @param fileNameLabel text displayed in front of the filename text field; or {@code null}
* @param fileName user-editable filename currently shown in the filename field; or {@code null}
* @param folder current directory shown in the dialog; or {@code null}
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
new file mode 100644
index 00000000..d5d6d773
--- /dev/null
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/SystemFileChooser.java
@@ -0,0 +1,559 @@
+/*
+ * Copyright 2025 FormDev Software GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.formdev.flatlaf.util;
+
+import java.awt.Component;
+import java.awt.SecondaryLoop;
+import java.awt.Toolkit;
+import java.awt.Window;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import javax.swing.filechooser.FileSystemView;
+import com.formdev.flatlaf.FlatSystemProperties;
+import com.formdev.flatlaf.ui.FlatNativeLinuxLibrary;
+import com.formdev.flatlaf.ui.FlatNativeWindowsLibrary;
+
+/**
+ * Gives access to operating system file dialogs.
+ *
+ * The API is (mostly) compatible with {@link JFileChooser}.
+ * To use this class in existing code, do a string replace from {@code JFileChooser} to {@code SystemFileChooser}.
+ * If there are no compile errors, then there is a good chance that it works without further changes.
+ * If there are compile errors, then you're using a feature that {@code SystemFileChooser} does not support.
+ *
+ * Supported platforms are Windows 10+, macOS 10.14+ and Linux GTK 3.
+ * {@code JFileChooser} is used on unsupported platforms.
+ *
+ * {@code SystemFileChooser} requires FlatLaf native libraries (usually contained in flatlaf.jar).
+ * If not available or disabled (via {@link FlatSystemProperties#USE_NATIVE_LIBRARY}
+ * or {@link FlatSystemProperties#USE_SYSTEM_FILE_CHOOSER}), then {@code JFileChooser} is used.
+ *
+ *
+ *
Limitations/incompatibilities compared to JFileChooser
+ *
+ *
+ * - 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.
+ *
- 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.
+ * It is not possible to customize that question dialog.
+ *
- Save File dialog does not support multi-selection.
+ *
- {@link JFileChooser#FILES_AND_DIRECTORIES} is not supported.
+ *
+ *
+ * @author Karl Tauber
+ * @since 3.6
+ */
+public class SystemFileChooser
+{
+ /** @see JFileChooser#OPEN_DIALOG */
+ public static final int OPEN_DIALOG = JFileChooser.OPEN_DIALOG;
+
+ /** @see JFileChooser#SAVE_DIALOG */
+ public static final int SAVE_DIALOG = JFileChooser.SAVE_DIALOG;
+
+ /** @see JFileChooser#CANCEL_OPTION */
+ public static final int CANCEL_OPTION = JFileChooser.CANCEL_OPTION;
+
+ /** @see JFileChooser#APPROVE_OPTION */
+ public static final int APPROVE_OPTION = JFileChooser.APPROVE_OPTION;
+
+ /** @see JFileChooser#FILES_ONLY */
+ public static final int FILES_ONLY = JFileChooser.FILES_ONLY;
+
+ /** @see JFileChooser#DIRECTORIES_ONLY */
+ public static final int DIRECTORIES_ONLY = JFileChooser.DIRECTORIES_ONLY;
+
+ private int dialogType = OPEN_DIALOG;
+ private String dialogTitle;
+ private String approveButtonText;
+ private int approveButtonMnemonic = 0;
+ private int fileSelectionMode = FILES_ONLY;
+ private boolean multiSelection;
+ private boolean useFileHiding = true;
+
+ private File currentDirectory;
+ private File selectedFile;
+ private File[] selectedFiles;
+
+ /** @see JFileChooser#JFileChooser() */
+ public SystemFileChooser() {
+ this( (File) null );
+ }
+
+ /** @see JFileChooser#JFileChooser(String) */
+ public SystemFileChooser( String currentDirectoryPath ) {
+ setCurrentDirectory( (currentDirectoryPath != null)
+ ? FileSystemView.getFileSystemView().createFileObject( currentDirectoryPath )
+ : null );
+ }
+
+ /** @see JFileChooser#JFileChooser(File) */
+ public SystemFileChooser( File currentDirectory ) {
+ setCurrentDirectory( currentDirectory );
+ }
+
+ /** @see JFileChooser#showOpenDialog(Component) */
+ public int showOpenDialog( Component parent ) {
+ setDialogType( OPEN_DIALOG );
+ return showDialogImpl( parent );
+ }
+
+ /** @see JFileChooser#showSaveDialog(Component) */
+ public int showSaveDialog( Component parent ) {
+ setDialogType( SAVE_DIALOG );
+ return showDialogImpl( parent );
+ }
+
+ /** @see JFileChooser#showDialog(Component, String) */
+ public int showDialog( Component parent, String approveButtonText ) {
+ if( approveButtonText != null )
+ setApproveButtonText( approveButtonText );
+ return showDialogImpl( parent );
+ }
+
+ /** @see JFileChooser#getDialogType() */
+ public int getDialogType() {
+ return dialogType;
+ }
+
+ /** @see JFileChooser#setDialogType(int) */
+ public void setDialogType( int dialogType ) {
+ if( dialogType != OPEN_DIALOG && dialogType != SAVE_DIALOG )
+ throw new IllegalArgumentException( "Invalid dialog type " + dialogType );
+
+ this.dialogType = dialogType;
+ }
+
+ /** @see JFileChooser#getDialogTitle() */
+ public String getDialogTitle() {
+ return dialogTitle;
+ }
+
+ /** @see JFileChooser#setDialogTitle(String) */
+ public void setDialogTitle( String dialogTitle ) {
+ this.dialogTitle = dialogTitle;
+ }
+
+ /** @see JFileChooser#getApproveButtonText() */
+ public String getApproveButtonText() {
+ return approveButtonText;
+ }
+
+ /** @see JFileChooser#setApproveButtonText(String) */
+ public void setApproveButtonText( String approveButtonText ) {
+ this.approveButtonText = approveButtonText;
+ }
+
+ /** @see JFileChooser#getApproveButtonMnemonic() */
+ public int getApproveButtonMnemonic() {
+ return approveButtonMnemonic;
+ }
+
+ /** @see JFileChooser#setApproveButtonMnemonic(int) */
+ public void setApproveButtonMnemonic( int mnemonic ) {
+ approveButtonMnemonic = mnemonic;
+ }
+
+ /** @see JFileChooser#setApproveButtonMnemonic(char) */
+ public void setApproveButtonMnemonic( char mnemonic ) {
+ int vk = mnemonic;
+ if( vk >= 'a' && vk <= 'z' )
+ vk -= 'a' - 'A';
+ setApproveButtonMnemonic( vk );
+ }
+
+ /** @see JFileChooser#getFileSelectionMode() */
+ public int getFileSelectionMode() {
+ return fileSelectionMode;
+ }
+
+ /** @see JFileChooser#setFileSelectionMode(int) */
+ public void setFileSelectionMode( int fileSelectionMode ) {
+ if( fileSelectionMode != FILES_ONLY && fileSelectionMode != DIRECTORIES_ONLY )
+ throw new IllegalArgumentException( "Invalid file selection mode " + fileSelectionMode );
+
+ this.fileSelectionMode = fileSelectionMode;
+ }
+
+ /** @see JFileChooser#isFileSelectionEnabled() */
+ public boolean isFileSelectionEnabled() {
+ return fileSelectionMode == FILES_ONLY;
+ }
+
+ /** @see JFileChooser#isDirectorySelectionEnabled() */
+ public boolean isDirectorySelectionEnabled() {
+ return fileSelectionMode == DIRECTORIES_ONLY;
+ }
+
+ /** @see JFileChooser#isMultiSelectionEnabled() */
+ public boolean isMultiSelectionEnabled() {
+ return multiSelection;
+ }
+
+ /** @see JFileChooser#setMultiSelectionEnabled(boolean) */
+ public void setMultiSelectionEnabled( boolean multiSelection ) {
+ this.multiSelection = multiSelection;
+ }
+
+ /** @see JFileChooser#isFileHidingEnabled() */
+ public boolean isFileHidingEnabled() {
+ return useFileHiding;
+ }
+
+ /** @see JFileChooser#setFileHidingEnabled(boolean) */
+ public void setFileHidingEnabled( boolean useFileHiding ) {
+ this.useFileHiding = useFileHiding;
+ }
+
+ /** @see JFileChooser#getCurrentDirectory() */
+ public File getCurrentDirectory() {
+ return currentDirectory;
+ }
+
+ /** @see JFileChooser#setCurrentDirectory(File) */
+ public void setCurrentDirectory( File dir ) {
+ // for compatibility with JFileChooser
+ if( dir != null && !dir.exists() )
+ return;
+ if( dir == null )
+ dir = FileSystemView.getFileSystemView().getDefaultDirectory();
+
+ currentDirectory = dir;
+ }
+
+ /** @see JFileChooser#getSelectedFile() */
+ public File getSelectedFile() {
+ return selectedFile;
+ }
+
+ /** @see JFileChooser#setSelectedFile(File) */
+ public void setSelectedFile( File file ) {
+ selectedFile = file;
+
+ // for compatibility with JFileChooser
+ if( file != null &&
+ file.isAbsolute() &&
+ !FileSystemView.getFileSystemView().isParent( getCurrentDirectory(), file ) )
+ setCurrentDirectory( file.getParentFile() );
+ }
+
+ /** @see JFileChooser#getSelectedFiles() */
+ public File[] getSelectedFiles() {
+ return (selectedFiles != null) ? selectedFiles.clone() : new File[0];
+ }
+
+ /** @see JFileChooser#setSelectedFiles(File[]) */
+ public void setSelectedFiles( File[] selectedFiles ) {
+ if( selectedFiles != null && selectedFiles.length > 0 ) {
+ this.selectedFiles = selectedFiles.clone();
+ setSelectedFile( selectedFiles[0] );
+ } else {
+ this.selectedFiles = null;
+ setSelectedFile( null );
+ }
+ }
+
+ private int showDialogImpl( Component parent ) {
+ File[] files = getProvider().showDialog( parent, this );
+ setSelectedFiles( files );
+ return (files != null) ? APPROVE_OPTION : CANCEL_OPTION;
+ }
+
+ private FileChooserProvider getProvider() {
+ if( !FlatSystemProperties.getBoolean( FlatSystemProperties.USE_SYSTEM_FILE_CHOOSER, true ) )
+ return new SwingFileChooserProvider();
+
+ if( SystemInfo.isWindows_10_orLater && FlatNativeWindowsLibrary.isLoaded() )
+ return new WindowsFileChooserProvider();
+ else if( SystemInfo.isLinux && FlatNativeLinuxLibrary.isLoaded() )
+ return new LinuxFileChooserProvider();
+ else // unknown platform or FlatLaf native library not loaded
+ return new SwingFileChooserProvider();
+ }
+
+ //---- interface FileChooserProvider --------------------------------------
+
+ private interface FileChooserProvider {
+ File[] showDialog( Component parent, SystemFileChooser fc );
+ }
+
+ //---- class SystemFileChooserProvider ------------------------------------
+
+ private static abstract class SystemFileChooserProvider
+ implements FileChooserProvider
+ {
+ @Override
+ public File[] showDialog( Component parent, SystemFileChooser fc ) {
+ Window owner = (parent instanceof Window)
+ ? (Window) parent
+ : (parent != null) ? SwingUtilities.windowForComponent( parent ) : null;
+ AtomicReference filenamesRef = new AtomicReference<>();
+
+ // create secondary event look and invoke system file dialog on a new thread
+ SecondaryLoop secondaryLoop = Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop();
+ new Thread( () -> {
+ filenamesRef.set( showSystemDialog( owner, fc ) );
+ secondaryLoop.exit();
+ } ).start();
+ secondaryLoop.enter();
+
+ String[] filenames = filenamesRef.get();
+
+ // fallback to Swing file chooser if system file dialog failed or is not available
+ if( filenames == null )
+ return new SwingFileChooserProvider().showDialog( parent, fc );
+
+ // canceled?
+ if( filenames.length == 0 )
+ return null;
+
+ // convert file names to file objects
+ FileSystemView fsv = FileSystemView.getFileSystemView();
+ File[] files = new File[filenames.length];
+ for( int i = 0; i < filenames.length; i++ )
+ files[i] = fsv.createFileObject( filenames[i] );
+ return files;
+ }
+
+ abstract String[] showSystemDialog( Window owner, SystemFileChooser fc );
+ }
+
+ //---- class WindowsFileChooserProvider -----------------------------------
+
+ private static class WindowsFileChooserProvider
+ extends SystemFileChooserProvider
+ {
+ @Override
+ String[] showSystemDialog( Window owner, SystemFileChooser fc ) {
+ boolean open = (fc.getDialogType() == OPEN_DIALOG);
+ String approveButtonText = fc.getApproveButtonText();
+ int approveButtonMnemonic = fc.getApproveButtonMnemonic();
+ String fileName = null;
+ String folder = null;
+ String saveAsItem = null;
+
+ // approve button text and mnemonic
+ if( approveButtonText != null ) {
+ approveButtonText = approveButtonText.replace( "&", "&&" );
+ if( approveButtonMnemonic > 0 ) {
+ int mnemonicIndex = approveButtonText.toUpperCase( Locale.ENGLISH ).indexOf( approveButtonMnemonic );
+ if( mnemonicIndex >= 0 ) {
+ approveButtonText = approveButtonText.substring( 0, mnemonicIndex )
+ + '&' + approveButtonText.substring( mnemonicIndex );
+ }
+ }
+ }
+
+ // paths
+ File currentDirectory = fc.getCurrentDirectory();
+ File selectedFile = fc.getSelectedFile();
+ if( selectedFile != null ) {
+ if( selectedFile.exists() && !open )
+ saveAsItem = selectedFile.getAbsolutePath();
+ else {
+ fileName = selectedFile.getName();
+ folder = selectedFile.getParent();
+ }
+ } else if( currentDirectory != null )
+ folder = currentDirectory.getAbsolutePath();
+
+ // options
+ int optionsSet = FlatNativeWindowsLibrary.FOS_OVERWRITEPROMPT;
+ int optionsClear = 0;
+ if( fc.isDirectorySelectionEnabled() )
+ optionsSet |= FlatNativeWindowsLibrary.FOS_PICKFOLDERS;
+ if( fc.isMultiSelectionEnabled() )
+ optionsSet |= FlatNativeWindowsLibrary.FOS_ALLOWMULTISELECT;
+ if( !fc.isFileHidingEnabled() )
+ optionsSet |= FlatNativeWindowsLibrary.FOS_FORCESHOWHIDDEN;
+
+ // filter
+ int fileTypeIndex = 0;
+ ArrayList fileTypes = new ArrayList<>();
+ // FOS_PICKFOLDERS does not support file types
+ if( !fc.isDirectorySelectionEnabled() ) {
+ // if there are no file types
+ // - for Save dialog add "All Files", otherwise Windows would show an empty "Save as type" combobox
+ // - for Open dialog, Windows hides the combobox
+ if( !open && fileTypes.isEmpty() ) {
+ fileTypes.add( UIManager.getString( "FileChooser.acceptAllFileFilterText" ) );
+ fileTypes.add( "*.*" );
+ }
+ }
+
+ // show system file dialog
+ return FlatNativeWindowsLibrary.showFileChooser( owner, open,
+ fc.getDialogTitle(), approveButtonText, null, fileName,
+ folder, saveAsItem, null, null, optionsSet, optionsClear,
+ fileTypeIndex, fileTypes.toArray( new String[fileTypes.size()] ) );
+ }
+ }
+
+ //---- class LinuxFileChooserProvider -----------------------------------..
+
+ private static class LinuxFileChooserProvider
+ extends SystemFileChooserProvider
+ {
+ @Override
+ String[] showSystemDialog( Window owner, SystemFileChooser fc ) {
+ boolean open = (fc.getDialogType() == OPEN_DIALOG);
+ String approveButtonText = fc.getApproveButtonText();
+ int approveButtonMnemonic = fc.getApproveButtonMnemonic();
+ String currentName = null;
+ String currentFolder = null;
+
+ // approve button text and mnemonic
+ if( approveButtonText != null ) {
+ approveButtonText = approveButtonText.replace( "_", "__" );
+ if( approveButtonMnemonic > 0 ) {
+ int mnemonicIndex = approveButtonText.toUpperCase( Locale.ENGLISH ).indexOf( approveButtonMnemonic );
+ if( mnemonicIndex >= 0 ) {
+ approveButtonText = approveButtonText.substring( 0, mnemonicIndex )
+ + '_' + approveButtonText.substring( mnemonicIndex );
+ }
+ }
+ }
+
+ // paths
+ File currentDirectory = fc.getCurrentDirectory();
+ File selectedFile = fc.getSelectedFile();
+ if( selectedFile != null ) {
+ if( selectedFile.isDirectory() )
+ currentFolder = selectedFile.getAbsolutePath();
+ else {
+ currentName = selectedFile.getName();
+ currentFolder = selectedFile.getParent();
+ }
+ } else if( currentDirectory != null )
+ currentFolder = currentDirectory.getAbsolutePath();
+
+ // options
+ int optionsSet = FlatNativeLinuxLibrary.FC_do_overwrite_confirmation;
+ int optionsClear = 0;
+ if( fc.isDirectorySelectionEnabled() )
+ optionsSet |= FlatNativeLinuxLibrary.FC_select_folder;
+ if( fc.isMultiSelectionEnabled() )
+ optionsSet |= FlatNativeLinuxLibrary.FC_select_multiple;
+ if( !fc.isFileHidingEnabled() )
+ optionsSet |= FlatNativeLinuxLibrary.FC_show_hidden;
+ else // necessary because GTK seems to be remember last state and re-use it for new file dialogs
+ optionsClear |= FlatNativeLinuxLibrary.FC_show_hidden;
+
+ // show system file dialog
+ return FlatNativeLinuxLibrary.showFileChooser( owner, open,
+ fc.getDialogTitle(), approveButtonText, currentName, currentFolder,
+ optionsSet, optionsClear, 0 );
+ }
+ }
+
+ //---- class SwingFileChooserProvider -------------------------------------
+
+ private static class SwingFileChooserProvider
+ implements FileChooserProvider
+ {
+ @Override
+ public File[] showDialog( Component parent, SystemFileChooser fc ) {
+ JFileChooser chooser = new JFileChooser() {
+ @Override
+ public void approveSelection() {
+ File[] files = isMultiSelectionEnabled()
+ ? getSelectedFiles()
+ : new File[] { getSelectedFile() };
+
+ if( getDialogType() == OPEN_DIALOG || isDirectorySelectionEnabled() ) {
+ if( !checkMustExist( this, files ) )
+ return;
+ } else {
+ if( !checkOverwrite( this, files ) )
+ return;
+ }
+ super.approveSelection();
+ }
+ };
+
+ chooser.setDialogType( fc.getDialogType() );
+ chooser.setDialogTitle( fc.getDialogTitle() );
+ chooser.setApproveButtonText( fc.getApproveButtonText() );
+ chooser.setApproveButtonMnemonic( fc.getApproveButtonMnemonic() );
+ chooser.setFileSelectionMode( fc.getFileSelectionMode() );
+ chooser.setMultiSelectionEnabled( fc.isMultiSelectionEnabled() );
+ chooser.setFileHidingEnabled( fc.isFileHidingEnabled() );
+
+ // system file dialogs do not support multi-selection for Save File dialogs
+ if( chooser.isMultiSelectionEnabled() &&
+ chooser.getDialogType() == JFileChooser.SAVE_DIALOG &&
+ !chooser.isDirectorySelectionEnabled() )
+ chooser.setMultiSelectionEnabled( false );
+
+ // paths
+ chooser.setCurrentDirectory( fc.getCurrentDirectory() );
+ chooser.setSelectedFile( fc.getSelectedFile() );
+
+ if( chooser.showDialog( parent, null ) != JFileChooser.APPROVE_OPTION )
+ return null;
+
+ return chooser.isMultiSelectionEnabled()
+ ? chooser.getSelectedFiles()
+ : new File[] { chooser.getSelectedFile() };
+ }
+ }
+
+ private static boolean checkMustExist( JFileChooser chooser, File[] files ) {
+ for( File file : files ) {
+ if( !file.exists() ) {
+ String title = chooser.getDialogTitle();
+ JOptionPane.showMessageDialog( chooser,
+ file.getName() + (chooser.isDirectorySelectionEnabled()
+ ? "\nPath does not exist.\nCheck the path and try again."
+ : "\nFile not found.\nCheck the file name and try again."),
+ (title != null) ? title : "Open",
+ JOptionPane.WARNING_MESSAGE );
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean checkOverwrite( JFileChooser chooser, File[] files ) {
+ for( File file : files ) {
+ if( file.exists() ) {
+ String title = chooser.getDialogTitle();
+ Locale l = chooser.getLocale();
+ Object[] options = {
+ UIManager.getString( "OptionPane.yesButtonText", l ),
+ UIManager.getString( "OptionPane.noButtonText", l ), };
+ int result = JOptionPane.showOptionDialog( chooser,
+ file.getName() + " already exists.\nDo you want to replace it?",
+ "Confirm " + (title != null ? title : "Save"),
+ JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE,
+ null, options, options[1] );
+ return (result == 0);
+ }
+ }
+ return true;
+ }
+}
diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java
index e9a9171a..9ec5e815 100644
--- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java
+++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java
@@ -49,6 +49,7 @@ import com.formdev.flatlaf.extras.FlatSVGUtils;
import com.formdev.flatlaf.util.ColorFunctions;
import com.formdev.flatlaf.util.FontUtils;
import com.formdev.flatlaf.util.LoggingFacade;
+import com.formdev.flatlaf.util.SystemFileChooser;
import com.formdev.flatlaf.util.SystemInfo;
import net.miginfocom.layout.ConstraintParser;
import net.miginfocom.layout.LC;
@@ -172,6 +173,16 @@ class DemoFrame
chooser.showSaveDialog( this );
}
+ private void openSystemActionPerformed() {
+ SystemFileChooser chooser = new SystemFileChooser();
+ chooser.showOpenDialog( this );
+ }
+
+ private void saveAsSystemActionPerformed() {
+ SystemFileChooser chooser = new SystemFileChooser();
+ chooser.showSaveDialog( this );
+ }
+
private void exitActionPerformed() {
dispose();
}
@@ -496,6 +507,8 @@ class DemoFrame
JMenuItem newMenuItem = new JMenuItem();
JMenuItem openMenuItem = new JMenuItem();
JMenuItem saveAsMenuItem = new JMenuItem();
+ JMenuItem openSystemMenuItem = new JMenuItem();
+ JMenuItem saveAsSystemMenuItem = new JMenuItem();
JMenuItem closeMenuItem = new JMenuItem();
exitMenuItem = new JMenuItem();
JMenu editMenu = new JMenu();
@@ -596,6 +609,17 @@ class DemoFrame
fileMenu.add(saveAsMenuItem);
fileMenu.addSeparator();
+ //---- openSystemMenuItem ----
+ openSystemMenuItem.setText("Open (System)...");
+ openSystemMenuItem.addActionListener(e -> openSystemActionPerformed());
+ fileMenu.add(openSystemMenuItem);
+
+ //---- saveAsSystemMenuItem ----
+ saveAsSystemMenuItem.setText("Save As (System)...");
+ saveAsSystemMenuItem.addActionListener(e -> saveAsSystemActionPerformed());
+ fileMenu.add(saveAsSystemMenuItem);
+ fileMenu.addSeparator();
+
//---- closeMenuItem ----
closeMenuItem.setText("Close");
closeMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd
index 08248c1d..61243782 100644
--- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd
+++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd
@@ -1,4 +1,4 @@
-JFDML JFormDesigner: "8.2.1.0.348" Java: "21.0.1" encoding: "UTF-8"
+JFDML JFormDesigner: "8.3" encoding: "UTF-8"
new FormModel {
contentType: "form/swing"
@@ -182,6 +182,19 @@ new FormModel {
"mnemonic": 83
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "saveAsActionPerformed", false ) )
} )
+ add( new FormComponent( "javax.swing.JPopupMenu$Separator" ) {
+ name: "separator9"
+ } )
+ add( new FormComponent( "javax.swing.JMenuItem" ) {
+ name: "openSystemMenuItem"
+ "text": "Open (System)..."
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "openSystemActionPerformed", false ) )
+ } )
+ add( new FormComponent( "javax.swing.JMenuItem" ) {
+ name: "saveAsSystemMenuItem"
+ "text": "Save As (System)..."
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "saveAsSystemActionPerformed", false ) )
+ } )
add( new FormComponent( "javax.swing.JPopupMenu$Separator" ) {
name: "separator2"
} )
diff --git a/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts b/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts
index 1ae7abbc..e541d58e 100644
--- a/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts
+++ b/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts
@@ -71,6 +71,17 @@ tasks {
else -> emptyList()
}
} )
+
+ doFirst {
+ // check required Java version
+ if( JavaVersion.current() < JavaVersion.VERSION_11 ) {
+ println()
+ println( "WARNING: Java 11 or later required to build Linux native library (running ${System.getProperty( "java.version" )})" )
+ println( " Native library built with older Java versions throw following exception when running in Java 17+:" )
+ println( " java.lang.UnsatisfiedLinkError: .../libjawt.so: version `SUNWprivate_1.1' not found" )
+ println()
+ }
+ }
}
withType().configureEach {
diff --git a/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/GtkFileChooser.cpp b/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/GtkFileChooser.cpp
index 01017446..858ef1b0 100644
--- a/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/GtkFileChooser.cpp
+++ b/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/GtkFileChooser.cpp
@@ -177,11 +177,11 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrar
bool multiSelect = isOptionSet( FC_select_multiple );
GtkWidget* dialog = gtk_file_chooser_dialog_new(
(ctitle != NULL) ? ctitle
- : (open ? (selectFolder ? (multiSelect ? _("Select Folders") : _("Select Folder"))
- : (multiSelect ? _("Open Files") : _("Open File"))) : _("Save File")),
+ : (selectFolder ? (multiSelect ? _("Select Folders") : _("Select Folder"))
+ : (open ? ((multiSelect ? _("Open Files") : _("Open File"))) : _("Save File"))),
NULL, // can not use AWT X11 window as parent because GtkWindow is required
- open ? (selectFolder ? GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER : GTK_FILE_CHOOSER_ACTION_OPEN)
- : GTK_FILE_CHOOSER_ACTION_SAVE,
+ selectFolder ? GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
+ : (open ? GTK_FILE_CHOOSER_ACTION_OPEN : GTK_FILE_CHOOSER_ACTION_SAVE),
_("_Cancel"), GTK_RESPONSE_CANCEL,
(cokButtonLabel != NULL) ? cokButtonLabel : (open ? _("_Open") : _("_Save")), GTK_RESPONSE_ACCEPT,
NULL ); // marks end of buttons
diff --git a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/WinFileChooser.cpp b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/WinFileChooser.cpp
index 03bc2b5b..613cb072 100644
--- a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/WinFileChooser.cpp
+++ b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/WinFileChooser.cpp
@@ -144,6 +144,7 @@ public:
//---- helper -----------------------------------------------------------------
+#define isOptionSet( option ) ((optionsSet & com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_ ## option) != 0)
#define CHECK_HRESULT( code ) { if( (code) != S_OK ) return NULL; }
jobjectArray newJavaStringArray( JNIEnv* env, jsize count ) {
@@ -170,12 +171,12 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibr
return NULL;
// handle limitations (without this, some Win32 method fails and this method returns NULL)
- if( (optionsSet & FOS_PICKFOLDERS) != 0 ) {
- if( open )
- fileTypes = NULL; // no filter allowed for picking folders
- else
- optionsSet &= ~FOS_PICKFOLDERS; // not allowed for save dialog
+ if( isOptionSet( FOS_PICKFOLDERS ) ) {
+ open = true; // always use IFileOpenDialog for picking folders
+ fileTypes = NULL; // no filter allowed for picking folders
}
+ if( !open && isOptionSet( FOS_ALLOWMULTISELECT ) )
+ optionsSet &= ~FOS_ALLOWMULTISELECT;
// convert Java strings to C strings
AutoReleaseString ctitle( env, title );
@@ -219,7 +220,7 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibr
CHECK_HRESULT( dialog->SetOptions ( (existingOptions & ~optionsClear) | optionsSet ) );
// initialize filter
- if( specs.count > 0 && (optionsSet & FOS_PICKFOLDERS) == 0 ) {
+ if( specs.count > 0 ) {
CHECK_HRESULT( dialog->SetFileTypes( specs.count, specs.specs ) );
if( fileTypeIndex > 0 )
CHECK_HRESULT( dialog->SetFileTypeIndex( min( fileTypeIndex + 1, specs.count ) ) );
diff --git a/flatlaf-testing/build.gradle.kts b/flatlaf-testing/build.gradle.kts
index 6fd1b64f..7215de8b 100644
--- a/flatlaf-testing/build.gradle.kts
+++ b/flatlaf-testing/build.gradle.kts
@@ -41,6 +41,7 @@ dependencies {
implementation( libs.jide.oss )
implementation( libs.glazedlists )
implementation( libs.netbeans.api.awt )
+ implementation( libs.nativejfilechooser )
components.all()
}
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
new file mode 100644
index 00000000..cb66fa12
--- /dev/null
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserTest.java
@@ -0,0 +1,749 @@
+/*
+ * Copyright 2025 FormDev Software GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.formdev.flatlaf.testing;
+
+import java.awt.Dialog;
+import java.awt.FileDialog;
+import java.awt.Frame;
+import java.awt.Window;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowFocusListener;
+import java.awt.event.WindowListener;
+import java.awt.event.WindowStateListener;
+import java.io.File;
+import java.util.Arrays;
+import java.util.function.Consumer;
+import java.util.prefs.Preferences;
+import java.util.stream.Stream;
+import javax.swing.*;
+import javax.swing.filechooser.FileFilter;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import com.formdev.flatlaf.FlatSystemProperties;
+import com.formdev.flatlaf.demo.DemoPrefs;
+import com.formdev.flatlaf.util.SystemFileChooser;
+import com.formdev.flatlaf.util.SystemInfo;
+import li.flor.nativejfilechooser.NativeJFileChooser;
+import net.miginfocom.swing.*;
+
+/**
+ * @author Karl Tauber
+ */
+public class FlatSystemFileChooserTest
+ extends FlatTestPanel
+{
+ public static void main( String[] args ) {
+ // macOS (see https://www.formdev.com/flatlaf/macos/)
+ if( SystemInfo.isMacOS ) {
+ // appearance of window title bars
+ // possible values:
+ // - "system": use current macOS appearance (light or dark)
+ // - "NSAppearanceNameAqua": use light appearance
+ // - "NSAppearanceNameDarkAqua": use dark appearance
+ // (needs to be set on main thread; setting it on AWT thread does not work)
+ System.setProperty( "apple.awt.application.appearance", "system" );
+ }
+
+ SwingUtilities.invokeLater( () -> {
+ FlatTestFrame frame = FlatTestFrame.create( args, "FlatSystemFileChooserTest" );
+ frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); // necessary because of JavaFX
+ addListeners( frame );
+ frame.showFrame( FlatSystemFileChooserTest::new );
+ } );
+ }
+
+ FlatSystemFileChooserTest() {
+ initComponents();
+
+ if( !NativeJFileChooser.FX_AVAILABLE ) {
+ javafxOpenButton.setEnabled( false );
+ javafxSaveButton.setEnabled( false );
+ }
+
+ Preferences state = DemoPrefs.getState();
+ currentDirField.setText( state.get( "systemfilechooser.currentdir", "" ) );
+ selectedFileField.setText( state.get( "systemfilechooser.selectedfile", "" ) );
+ selectedFilesField.setText( state.get( "systemfilechooser.selectedfiles", "" ) );
+ currentDirCheckBox.setSelected( state.getBoolean( "systemfilechooser.currentdir.enabled", false ) );
+ selectedFileCheckBox.setSelected( state.getBoolean( "systemfilechooser.selectedfile.enabled", false ) );
+ selectedFilesCheckBox.setSelected( state.getBoolean( "systemfilechooser.selectedfiles.enabled", false ) );
+
+ currentDirChanged();
+ selectedFileChanged();
+ selectedFilesChanged();
+ }
+
+ private void open() {
+ SystemFileChooser fc = new SystemFileChooser();
+ configureSystemFileChooser( fc );
+ showWithOwner( owner -> {
+ int result = fc.showOpenDialog( owner );
+ outputSystemFileChooser( fc, result );
+ } );
+ }
+
+ private void save() {
+ SystemFileChooser fc = new SystemFileChooser();
+ configureSystemFileChooser( fc );
+ showWithOwner( owner -> {
+ int result = fc.showSaveDialog( owner );
+ outputSystemFileChooser( fc, result );
+ } );
+ }
+
+ private void swingOpen() {
+ JFileChooser fc = new JFileChooser();
+ configureSwingFileChooser( fc );
+ showWithOwner( owner -> {
+ int result = fc.showOpenDialog( owner );
+ outputSwingFileChooser( "Swing", fc, result );
+ } );
+ }
+
+ private void swingSave() {
+ JFileChooser fc = new JFileChooser();
+ configureSwingFileChooser( fc );
+ showWithOwner( owner -> {
+ int result = fc.showSaveDialog( owner );
+ outputSwingFileChooser( "Swing", fc, result );
+ } );
+ }
+
+ private void awtOpen() {
+ showWithOwner( owner -> {
+ FileDialog fc = (owner instanceof Frame)
+ ? new FileDialog( (Frame) owner )
+ : new FileDialog( (Dialog) owner );
+ configureAWTFileChooser( fc, true );
+ fc.setVisible( true );
+ outputAWTFileChooser( fc );
+ } );
+ }
+
+ private void awtSave() {
+ showWithOwner( owner -> {
+ FileDialog fc = (owner instanceof Frame)
+ ? new FileDialog( (Frame) owner )
+ : new FileDialog( (Dialog) owner );
+ configureAWTFileChooser( fc, false );
+ fc.setVisible( true );
+ outputAWTFileChooser( fc );
+ } );
+ }
+
+ private void javafxOpen() {
+ JFileChooser fc = new NativeJFileChooser();
+ configureSwingFileChooser( fc );
+ showWithOwner( owner -> {
+ int result = fc.showOpenDialog( owner );
+ outputSwingFileChooser( "JavaFX", fc, result );
+ } );
+ }
+
+ private void javafxSave() {
+ JFileChooser fc = new NativeJFileChooser();
+ configureSwingFileChooser( fc );
+ showWithOwner( owner -> {
+ int result = fc.showSaveDialog( owner );
+ outputSwingFileChooser( "JavaFX", fc, result );
+ } );
+ }
+
+ private void configureSystemFileChooser( SystemFileChooser fc ) {
+ fc.setDialogTitle( n( dialogTitleField.getText() ) );
+ fc.setApproveButtonText( n( approveButtonTextField.getText() ) );
+ fc.setApproveButtonMnemonic( mnemonic( approveButtonMnemonicField.getText() ) );
+
+ // paths
+ if( currentDirCheckBox.isSelected() )
+ fc.setCurrentDirectory( toFile( currentDirField.getText() ) );
+ if( selectedFileCheckBox.isSelected() )
+ fc.setSelectedFile( toFile( selectedFileField.getText() ) );
+ if( selectedFilesCheckBox.isSelected() )
+ fc.setSelectedFiles( toFiles( selectedFilesField.getText() ) );
+
+ // options
+ if( directorySelectionCheckBox.isSelected() )
+ fc.setFileSelectionMode( SystemFileChooser.DIRECTORIES_ONLY );
+ fc.setMultiSelectionEnabled( multiSelectionEnabledCheckBox.isSelected() );
+ fc.setFileHidingEnabled( useFileHidingCheckBox.isSelected() );
+ if( useSystemFileChooserCheckBox.isSelected() )
+ System.clearProperty( FlatSystemProperties.USE_SYSTEM_FILE_CHOOSER );
+ else
+ System.setProperty( FlatSystemProperties.USE_SYSTEM_FILE_CHOOSER, "false" );
+
+ //TODO filter
+ }
+
+ private void configureSwingFileChooser( JFileChooser fc ) {
+ fc.setDialogTitle( n( dialogTitleField.getText() ) );
+ fc.setApproveButtonText( n( approveButtonTextField.getText() ) );
+ fc.setApproveButtonMnemonic( mnemonic( approveButtonMnemonicField.getText() ) );
+
+ // paths
+ if( currentDirCheckBox.isSelected() )
+ fc.setCurrentDirectory( toFile( currentDirField.getText() ) );
+ if( selectedFileCheckBox.isSelected() )
+ fc.setSelectedFile( toFile( selectedFileField.getText() ) );
+ if( selectedFilesCheckBox.isSelected() )
+ fc.setSelectedFiles( toFiles( selectedFilesField.getText() ) );
+
+ // options
+ if( directorySelectionCheckBox.isSelected() )
+ fc.setFileSelectionMode( JFileChooser.DIRECTORIES_ONLY );
+ fc.setMultiSelectionEnabled( multiSelectionEnabledCheckBox.isSelected() );
+ fc.setFileHidingEnabled( useFileHidingCheckBox.isSelected() );
+
+ // filter
+ String fileTypesStr = n( (String) fileTypesField.getSelectedItem() );
+ String[] fileTypes = {};
+ if( fileTypesStr != null )
+ fileTypes = fileTypesStr.trim().split( "[,]+" );
+ int fileTypeIndex = fileTypeIndexSlider.getValue();
+ if( !useAcceptAllFileFilterCheckBox.isSelected() )
+ fc.setAcceptAllFileFilterUsed( false );
+ for( int i = 0; i < fileTypes.length; i += 2 ) {
+ fc.addChoosableFileFilter( "*".equals( fileTypes[i+1] )
+ ? fc.getAcceptAllFileFilter()
+ : new FileNameExtensionFilter( fileTypes[i], fileTypes[i+1].split( ";" ) ) );
+ }
+ FileFilter[] filters = fc.getChoosableFileFilters();
+ if( filters.length > 0 )
+ fc.setFileFilter( filters[Math.min( Math.max( fileTypeIndex, 0 ), filters.length - 1 )] );
+ }
+
+ private void configureAWTFileChooser( FileDialog fc, boolean open ) {
+ fc.setMode( open ? FileDialog.LOAD : FileDialog.SAVE );
+ fc.setTitle( n( dialogTitleField.getText() ) );
+
+ // paths
+ if( currentDirCheckBox.isSelected() )
+ fc.setDirectory( n( currentDirField.getText() ) );
+
+ // options
+ fc.setMultipleMode( multiSelectionEnabledCheckBox.isSelected() );
+ }
+
+ private void outputSystemFileChooser( SystemFileChooser fc, int result ) {
+ output( "System", fc.getDialogType() == SystemFileChooser.OPEN_DIALOG,
+ fc.isDirectorySelectionEnabled(), fc.isMultiSelectionEnabled(),
+ "result", result,
+ "currentDirectory", fc.getCurrentDirectory(),
+ "selectedFile", fc.getSelectedFile(),
+ "selectedFiles", fc.getSelectedFiles() );
+ }
+
+ private void outputSwingFileChooser( String type, JFileChooser fc, int result ) {
+ output( type, fc.getDialogType() == JFileChooser.OPEN_DIALOG,
+ fc.isDirectorySelectionEnabled(), fc.isMultiSelectionEnabled(),
+ "result", result,
+ "currentDirectory", fc.getCurrentDirectory(),
+ "selectedFile", fc.getSelectedFile(),
+ "selectedFiles", fc.getSelectedFiles() );
+ }
+
+ private void outputAWTFileChooser( FileDialog fc ) {
+ output( "AWT", fc.getMode() == FileDialog.LOAD, false, fc.isMultipleMode(),
+ "files", fc.getFiles(),
+ "directory", fc.getDirectory(),
+ "file", fc.getFile() );
+ }
+
+ private void output( String type, boolean open, boolean directorySelection,
+ boolean multiSelection, Object... values )
+ {
+ outputField.append( "---- " + type + " " + (open ? "Open " : "Save ")
+ + (directorySelection ? " directory-sel " : "")
+ + (multiSelection ? " multi-sel " : "")
+ + "----\n" );
+
+ for( int i = 0; i < values.length; i += 2 ) {
+ outputField.append( values[i] + " = " );
+ Object value = values[i+1];
+ if( value instanceof File[] )
+ outputField.append( Arrays.toString( (File[]) value ).replace( ",", "\n " ) );
+ else
+ outputField.append( String.valueOf( value ) );
+ outputField.append( "\n" );
+ }
+ outputField.append( "\n" );
+ outputField.setCaretPosition( outputField.getDocument().getLength() );
+ }
+
+ private static String n( String s ) {
+ return !s.isEmpty() ? s : null;
+ }
+
+ private static char mnemonic( String s ) {
+ return !s.isEmpty() ? s.charAt( 0 ) : 0;
+ }
+
+ private void showWithOwner( Consumer showConsumer ) {
+ Window frame = SwingUtilities.windowForComponent( this );
+ if( ownerFrameRadioButton.isSelected() )
+ showConsumer.accept( frame );
+ else if( ownerDialogRadioButton.isSelected() ) {
+ JDialog dialog = new JDialog( frame, "Dummy Modal Dialog", Dialog.DEFAULT_MODALITY_TYPE );
+ dialog.setDefaultCloseOperation( JDialog.DISPOSE_ON_CLOSE );
+ dialog.addWindowListener( new WindowAdapter() {
+ @Override
+ public void windowOpened( WindowEvent e ) {
+ showConsumer.accept( dialog );
+ }
+ } );
+ dialog.setSize( 1200, 1000 );
+ dialog.setLocationRelativeTo( this );
+ dialog.setVisible( true );
+ } else
+ showConsumer.accept( null );
+ }
+
+ private void currentDirChanged() {
+ boolean b = currentDirCheckBox.isSelected();
+ currentDirField.setEditable( b );
+ currentDirChooseButton.setEnabled( b );
+
+ DemoPrefs.getState().putBoolean( "systemfilechooser.currentdir.enabled", b );
+ }
+
+ private void selectedFileChanged() {
+ boolean b = selectedFileCheckBox.isSelected();
+ selectedFileField.setEditable( b );
+ selectedFileChooseButton.setEnabled( b );
+
+ DemoPrefs.getState().putBoolean( "systemfilechooser.selectedfile.enabled", b );
+ }
+
+ private void selectedFilesChanged() {
+ boolean b = selectedFilesCheckBox.isSelected();
+ selectedFilesField.setEditable( b );
+ selectedFilesChooseButton.setEnabled( b );
+
+ DemoPrefs.getState().putBoolean( "systemfilechooser.selectedfiles.enabled", b );
+ }
+
+ private void chooseCurrentDir() {
+ JFileChooser chooser = new JFileChooser();
+ chooser.setDialogTitle( "Current Directory" );
+ chooser.setSelectedFile( toFile( currentDirField.getText() ) );
+ chooser.setFileSelectionMode( JFileChooser.DIRECTORIES_ONLY );
+ if( chooser.showOpenDialog( this ) == JFileChooser.APPROVE_OPTION ) {
+ currentDirField.setText( toString( chooser.getSelectedFile() ) );
+ putState( "systemfilechooser.currentdir", currentDirField.getText() );
+ }
+ }
+
+ private void chooseSelectedFile() {
+ JFileChooser chooser = new JFileChooser();
+ chooser.setDialogTitle( "Selected File" );
+ chooser.setSelectedFile( toFile( selectedFileField.getText() ) );
+ chooser.setFileSelectionMode( JFileChooser.FILES_ONLY );
+ if( chooser.showOpenDialog( this ) == JFileChooser.APPROVE_OPTION ) {
+ selectedFileField.setText( toString( chooser.getSelectedFile() ) );
+ putState( "systemfilechooser.selectedfile", selectedFileField.getText() );
+ }
+ }
+
+ private void chooseSelectedFiles() {
+ JFileChooser chooser = new JFileChooser();
+ chooser.setDialogTitle( "Selected Files" );
+ chooser.setSelectedFiles( toFiles( selectedFilesField.getText() ) );
+ chooser.setFileSelectionMode( JFileChooser.FILES_ONLY );
+ chooser.setMultiSelectionEnabled( true );
+ if( chooser.showOpenDialog( this ) == JFileChooser.APPROVE_OPTION ) {
+ selectedFilesField.setText( toString( chooser.getSelectedFiles() ) );
+ putState( "systemfilechooser.selectedfiles", selectedFilesField.getText() );
+ }
+ }
+
+ private static File toFile( String s ) {
+ return !s.isEmpty() ? new File( s ) : null;
+ }
+
+ private static String toString( File file ) {
+ return (file != null) ? file.getAbsolutePath() : null;
+ }
+
+ private static File[] toFiles( String s ) {
+ return !s.isEmpty()
+ ? Stream.of( s.split( "," ) ).map( name -> new File( name ) ).toArray( File[]::new )
+ : new File[0];
+ }
+
+ private static String toString( File[] files ) {
+ return (files != null && files.length > 0)
+ ? String.join( ",", Stream.of( files ).map( file -> file.getAbsolutePath() ).toArray( String[]::new ) )
+ : "";
+ }
+
+ private static void putState( String key, String value ) {
+ if( value.isEmpty() )
+ DemoPrefs.getState().remove( key );
+ else
+ DemoPrefs.getState().put( key, value );
+ }
+
+ private static void addListeners( Window w ) {
+ w.addWindowListener( new WindowListener() {
+ @Override
+ public void windowOpened( WindowEvent e ) {
+ System.out.println( e );
+ }
+
+ @Override
+ public void windowIconified( WindowEvent e ) {
+ System.out.println( e );
+ }
+
+ @Override
+ public void windowDeiconified( WindowEvent e ) {
+ System.out.println( e );
+ }
+
+ @Override
+ public void windowDeactivated( WindowEvent e ) {
+ System.out.println( e );
+ }
+
+ @Override
+ public void windowClosing( WindowEvent e ) {
+ System.out.println( e );
+ }
+
+ @Override
+ public void windowClosed( WindowEvent e ) {
+ System.out.println( e );
+ }
+
+ @Override
+ public void windowActivated( WindowEvent e ) {
+ System.out.println( e );
+ }
+ } );
+ w.addWindowStateListener( new WindowStateListener() {
+ @Override
+ public void windowStateChanged( WindowEvent e ) {
+ System.out.println( e );
+ }
+ } );
+ w.addWindowFocusListener( new WindowFocusListener() {
+ @Override
+ public void windowLostFocus( WindowEvent e ) {
+ System.out.println( e );
+ }
+
+ @Override
+ public void windowGainedFocus( WindowEvent e ) {
+ System.out.println( e );
+ }
+ } );
+ }
+
+ private void initComponents() {
+ // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
+ ownerLabel = new JLabel();
+ ownerFrameRadioButton = new JRadioButton();
+ ownerDialogRadioButton = new JRadioButton();
+ ownerNullRadioButton = new JRadioButton();
+ ownerSpacer = new JPanel(null);
+ dialogTitleLabel = new JLabel();
+ dialogTitleField = new JTextField();
+ panel1 = new JPanel();
+ directorySelectionCheckBox = new JCheckBox();
+ multiSelectionEnabledCheckBox = new JCheckBox();
+ useFileHidingCheckBox = new JCheckBox();
+ useSystemFileChooserCheckBox = new JCheckBox();
+ approveButtonTextLabel = new JLabel();
+ approveButtonTextField = new JTextField();
+ approveButtonMnemonicLabel = new JLabel();
+ approveButtonMnemonicField = new JTextField();
+ currentDirCheckBox = new JCheckBox();
+ currentDirField = new JTextField();
+ currentDirChooseButton = new JButton();
+ selectedFileCheckBox = new JCheckBox();
+ selectedFileField = new JTextField();
+ selectedFileChooseButton = new JButton();
+ selectedFilesCheckBox = new JCheckBox();
+ selectedFilesField = new JTextField();
+ selectedFilesChooseButton = new JButton();
+ fileTypesLabel = new JLabel();
+ fileTypesField = new JComboBox<>();
+ fileTypeIndexLabel = new JLabel();
+ fileTypeIndexSlider = new JSlider();
+ useAcceptAllFileFilterCheckBox = new JCheckBox();
+ openButton = new JButton();
+ saveButton = new JButton();
+ swingOpenButton = new JButton();
+ swingSaveButton = new JButton();
+ awtOpenButton = new JButton();
+ awtSaveButton = new JButton();
+ javafxOpenButton = new JButton();
+ javafxSaveButton = new JButton();
+ outputScrollPane = new JScrollPane();
+ outputField = new JTextArea();
+
+ //======== this ========
+ setLayout(new MigLayout(
+ "ltr,insets dialog,hidemode 3",
+ // columns
+ "[left]" +
+ "[grow,fill]" +
+ "[fill]",
+ // rows
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[grow,fill]"));
+
+ //---- ownerLabel ----
+ ownerLabel.setText("owner");
+ add(ownerLabel, "cell 0 0");
+
+ //---- ownerFrameRadioButton ----
+ ownerFrameRadioButton.setText("JFrame");
+ ownerFrameRadioButton.setSelected(true);
+ add(ownerFrameRadioButton, "cell 1 0");
+
+ //---- ownerDialogRadioButton ----
+ ownerDialogRadioButton.setText("JDialog");
+ add(ownerDialogRadioButton, "cell 1 0");
+
+ //---- ownerNullRadioButton ----
+ ownerNullRadioButton.setText("null");
+ add(ownerNullRadioButton, "cell 1 0");
+ add(ownerSpacer, "cell 1 0,growx");
+
+ //---- dialogTitleLabel ----
+ dialogTitleLabel.setText("dialogTitle");
+ add(dialogTitleLabel, "cell 0 1");
+ add(dialogTitleField, "cell 1 1");
+
+ //======== panel1 ========
+ {
+ panel1.setLayout(new MigLayout(
+ "insets 2,hidemode 3",
+ // columns
+ "[left]",
+ // rows
+ "[]0" +
+ "[]0" +
+ "[]" +
+ "[]"));
+
+ //---- directorySelectionCheckBox ----
+ directorySelectionCheckBox.setText("directorySelection");
+ panel1.add(directorySelectionCheckBox, "cell 0 0");
+
+ //---- multiSelectionEnabledCheckBox ----
+ multiSelectionEnabledCheckBox.setText("multiSelectionEnabled");
+ panel1.add(multiSelectionEnabledCheckBox, "cell 0 1");
+
+ //---- useFileHidingCheckBox ----
+ useFileHidingCheckBox.setText("useFileHiding");
+ useFileHidingCheckBox.setSelected(true);
+ panel1.add(useFileHidingCheckBox, "cell 0 2");
+
+ //---- useSystemFileChooserCheckBox ----
+ useSystemFileChooserCheckBox.setText("use SystemFileChooser");
+ useSystemFileChooserCheckBox.setSelected(true);
+ panel1.add(useSystemFileChooserCheckBox, "cell 0 3");
+ }
+ add(panel1, "cell 2 1 1 7,aligny top,growy 0");
+
+ //---- approveButtonTextLabel ----
+ approveButtonTextLabel.setText("approveButtonText");
+ add(approveButtonTextLabel, "cell 0 2");
+ add(approveButtonTextField, "cell 1 2,growx");
+
+ //---- approveButtonMnemonicLabel ----
+ approveButtonMnemonicLabel.setText("approveButtonMnemonic");
+ add(approveButtonMnemonicLabel, "cell 1 2");
+
+ //---- approveButtonMnemonicField ----
+ approveButtonMnemonicField.setColumns(3);
+ add(approveButtonMnemonicField, "cell 1 2");
+
+ //---- currentDirCheckBox ----
+ currentDirCheckBox.setText("current directory");
+ currentDirCheckBox.addActionListener(e -> currentDirChanged());
+ add(currentDirCheckBox, "cell 0 3");
+ add(currentDirField, "cell 1 3,growx");
+
+ //---- currentDirChooseButton ----
+ currentDirChooseButton.setText("...");
+ currentDirChooseButton.addActionListener(e -> chooseCurrentDir());
+ add(currentDirChooseButton, "cell 1 3");
+
+ //---- selectedFileCheckBox ----
+ selectedFileCheckBox.setText("selected file");
+ selectedFileCheckBox.addActionListener(e -> selectedFileChanged());
+ add(selectedFileCheckBox, "cell 0 4");
+ add(selectedFileField, "cell 1 4,growx");
+
+ //---- selectedFileChooseButton ----
+ selectedFileChooseButton.setText("...");
+ selectedFileChooseButton.addActionListener(e -> chooseSelectedFile());
+ add(selectedFileChooseButton, "cell 1 4");
+
+ //---- selectedFilesCheckBox ----
+ selectedFilesCheckBox.setText("selected files");
+ selectedFilesCheckBox.addActionListener(e -> selectedFilesChanged());
+ add(selectedFilesCheckBox, "cell 0 5");
+ add(selectedFilesField, "cell 1 5,growx");
+
+ //---- selectedFilesChooseButton ----
+ selectedFilesChooseButton.setText("...");
+ selectedFilesChooseButton.addActionListener(e -> chooseSelectedFiles());
+ add(selectedFilesChooseButton, "cell 1 5");
+
+ //---- fileTypesLabel ----
+ fileTypesLabel.setText("fileTypes");
+ add(fileTypesLabel, "cell 0 6");
+
+ //---- fileTypesField ----
+ fileTypesField.setEditable(true);
+ fileTypesField.setModel(new DefaultComboBoxModel<>(new String[] {
+ "Text Files,txt",
+ "All Files,*",
+ "Text Files,txt,PDF Files,pdf,All Files,*",
+ "Text and PDF Files,txt;pdf"
+ }));
+ add(fileTypesField, "cell 1 6");
+
+ //---- fileTypeIndexLabel ----
+ fileTypeIndexLabel.setText("fileTypeIndex");
+ add(fileTypeIndexLabel, "cell 0 7");
+
+ //---- fileTypeIndexSlider ----
+ fileTypeIndexSlider.setMaximum(10);
+ fileTypeIndexSlider.setMajorTickSpacing(1);
+ fileTypeIndexSlider.setValue(0);
+ fileTypeIndexSlider.setPaintLabels(true);
+ fileTypeIndexSlider.setSnapToTicks(true);
+ add(fileTypeIndexSlider, "cell 1 7,growx");
+
+ //---- useAcceptAllFileFilterCheckBox ----
+ useAcceptAllFileFilterCheckBox.setText("useAcceptAllFileFilter");
+ useAcceptAllFileFilterCheckBox.setSelected(true);
+ add(useAcceptAllFileFilterCheckBox, "cell 1 7");
+
+ //---- openButton ----
+ openButton.setText("Open...");
+ openButton.addActionListener(e -> open());
+ add(openButton, "cell 0 8 3 1");
+
+ //---- saveButton ----
+ saveButton.setText("Save...");
+ saveButton.addActionListener(e -> save());
+ add(saveButton, "cell 0 8 3 1");
+
+ //---- swingOpenButton ----
+ swingOpenButton.setText("Swing Open...");
+ swingOpenButton.addActionListener(e -> swingOpen());
+ add(swingOpenButton, "cell 0 8 3 1");
+
+ //---- swingSaveButton ----
+ swingSaveButton.setText("Swing Save...");
+ swingSaveButton.addActionListener(e -> swingSave());
+ add(swingSaveButton, "cell 0 8 3 1");
+
+ //---- awtOpenButton ----
+ awtOpenButton.setText("AWT Open...");
+ awtOpenButton.addActionListener(e -> awtOpen());
+ add(awtOpenButton, "cell 0 8 3 1");
+
+ //---- awtSaveButton ----
+ awtSaveButton.setText("AWT Save...");
+ awtSaveButton.addActionListener(e -> awtSave());
+ add(awtSaveButton, "cell 0 8 3 1");
+
+ //---- javafxOpenButton ----
+ javafxOpenButton.setText("JavaFX Open...");
+ javafxOpenButton.addActionListener(e -> javafxOpen());
+ add(javafxOpenButton, "cell 0 8 3 1");
+
+ //---- javafxSaveButton ----
+ javafxSaveButton.setText("JavaFX Save...");
+ javafxSaveButton.addActionListener(e -> javafxSave());
+ add(javafxSaveButton, "cell 0 8 3 1");
+
+ //======== outputScrollPane ========
+ {
+
+ //---- outputField ----
+ outputField.setRows(20);
+ outputScrollPane.setViewportView(outputField);
+ }
+ add(outputScrollPane, "cell 0 9 3 1,growx");
+
+ //---- ownerButtonGroup ----
+ ButtonGroup ownerButtonGroup = new ButtonGroup();
+ ownerButtonGroup.add(ownerFrameRadioButton);
+ ownerButtonGroup.add(ownerDialogRadioButton);
+ ownerButtonGroup.add(ownerNullRadioButton);
+ // JFormDesigner - End of component initialization //GEN-END:initComponents
+ }
+
+ // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
+ private JLabel ownerLabel;
+ private JRadioButton ownerFrameRadioButton;
+ private JRadioButton ownerDialogRadioButton;
+ private JRadioButton ownerNullRadioButton;
+ private JPanel ownerSpacer;
+ private JLabel dialogTitleLabel;
+ private JTextField dialogTitleField;
+ private JPanel panel1;
+ private JCheckBox directorySelectionCheckBox;
+ private JCheckBox multiSelectionEnabledCheckBox;
+ private JCheckBox useFileHidingCheckBox;
+ private JCheckBox useSystemFileChooserCheckBox;
+ private JLabel approveButtonTextLabel;
+ private JTextField approveButtonTextField;
+ private JLabel approveButtonMnemonicLabel;
+ private JTextField approveButtonMnemonicField;
+ private JCheckBox currentDirCheckBox;
+ private JTextField currentDirField;
+ private JButton currentDirChooseButton;
+ private JCheckBox selectedFileCheckBox;
+ private JTextField selectedFileField;
+ private JButton selectedFileChooseButton;
+ private JCheckBox selectedFilesCheckBox;
+ private JTextField selectedFilesField;
+ private JButton selectedFilesChooseButton;
+ private JLabel fileTypesLabel;
+ private JComboBox fileTypesField;
+ private JLabel fileTypeIndexLabel;
+ private JSlider fileTypeIndexSlider;
+ private JCheckBox useAcceptAllFileFilterCheckBox;
+ private JButton openButton;
+ private JButton saveButton;
+ private JButton swingOpenButton;
+ private JButton swingSaveButton;
+ private JButton awtOpenButton;
+ private JButton awtSaveButton;
+ private JButton javafxOpenButton;
+ private JButton javafxSaveButton;
+ private JScrollPane outputScrollPane;
+ private JTextArea outputField;
+ // JFormDesigner - End of variables declaration //GEN-END:variables
+}
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserTest.jfd
new file mode 100644
index 00000000..2aa7c66b
--- /dev/null
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserTest.jfd
@@ -0,0 +1,321 @@
+JFDML JFormDesigner: "8.3" encoding: "UTF-8"
+
+new FormModel {
+ contentType: "form/swing"
+ root: new FormRoot {
+ add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
+ "$layoutConstraints": "ltr,insets dialog,hidemode 3"
+ "$columnConstraints": "[left][grow,fill][fill]"
+ "$rowConstraints": "[][][][][][][][][][grow,fill]"
+ } ) {
+ name: "this"
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "ownerLabel"
+ "text": "owner"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 0"
+ } )
+ add( new FormComponent( "javax.swing.JRadioButton" ) {
+ name: "ownerFrameRadioButton"
+ "text": "JFrame"
+ "selected": true
+ "$buttonGroup": new FormReference( "ownerButtonGroup" )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 0"
+ } )
+ add( new FormComponent( "javax.swing.JRadioButton" ) {
+ name: "ownerDialogRadioButton"
+ "text": "JDialog"
+ "$buttonGroup": new FormReference( "ownerButtonGroup" )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 0"
+ } )
+ add( new FormComponent( "javax.swing.JRadioButton" ) {
+ name: "ownerNullRadioButton"
+ "text": "null"
+ "$buttonGroup": new FormReference( "ownerButtonGroup" )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 0"
+ } )
+ add( new FormComponent( "com.jformdesigner.designer.wrapper.HSpacer" ) {
+ name: "ownerSpacer"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 0,growx"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "dialogTitleLabel"
+ "text": "dialogTitle"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 1"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "dialogTitleField"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 1"
+ } )
+ add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
+ "$layoutConstraints": "insets 2,hidemode 3"
+ "$columnConstraints": "[left]"
+ "$rowConstraints": "[]0[]0[][]"
+ } ) {
+ name: "panel1"
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "directorySelectionCheckBox"
+ "text": "directorySelection"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 0"
+ } )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "multiSelectionEnabledCheckBox"
+ "text": "multiSelectionEnabled"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 1"
+ } )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "useFileHidingCheckBox"
+ "text": "useFileHiding"
+ "selected": true
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 2"
+ } )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "useSystemFileChooserCheckBox"
+ "text": "use SystemFileChooser"
+ "selected": true
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 3"
+ } )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 2 1 1 7,aligny top,growy 0"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "approveButtonTextLabel"
+ "text": "approveButtonText"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 2"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "approveButtonTextField"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 2,growx"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "approveButtonMnemonicLabel"
+ "text": "approveButtonMnemonic"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 2"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "approveButtonMnemonicField"
+ "columns": 3
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 2"
+ } )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "currentDirCheckBox"
+ "text": "current directory"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "currentDirChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 3"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "currentDirField"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 3,growx"
+ } )
+ add( new FormComponent( "javax.swing.JButton" ) {
+ name: "currentDirChooseButton"
+ "text": "..."
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "chooseCurrentDir", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 3"
+ } )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "selectedFileCheckBox"
+ "text": "selected file"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "selectedFileChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 4"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "selectedFileField"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 4,growx"
+ } )
+ add( new FormComponent( "javax.swing.JButton" ) {
+ name: "selectedFileChooseButton"
+ "text": "..."
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "chooseSelectedFile", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 4"
+ } )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "selectedFilesCheckBox"
+ "text": "selected files"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "selectedFilesChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 5"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "selectedFilesField"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 5,growx"
+ } )
+ add( new FormComponent( "javax.swing.JButton" ) {
+ name: "selectedFilesChooseButton"
+ "text": "..."
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "chooseSelectedFiles", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 5"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "fileTypesLabel"
+ "text": "fileTypes"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 6"
+ } )
+ add( new FormComponent( "javax.swing.JComboBox" ) {
+ name: "fileTypesField"
+ "editable": true
+ "model": new javax.swing.DefaultComboBoxModel {
+ selectedItem: "Text Files,txt"
+ addElement( "Text Files,txt" )
+ addElement( "All Files,*" )
+ addElement( "Text Files,txt,PDF Files,pdf,All Files,*" )
+ addElement( "Text and PDF Files,txt;pdf" )
+ }
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 6"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "fileTypeIndexLabel"
+ "text": "fileTypeIndex"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 7"
+ } )
+ add( new FormComponent( "javax.swing.JSlider" ) {
+ name: "fileTypeIndexSlider"
+ "maximum": 10
+ "majorTickSpacing": 1
+ "value": 0
+ "paintLabels": true
+ "snapToTicks": true
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 7,growx"
+ } )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "useAcceptAllFileFilterCheckBox"
+ "text": "useAcceptAllFileFilter"
+ "selected": true
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 7"
+ } )
+ add( new FormComponent( "javax.swing.JButton" ) {
+ name: "openButton"
+ "text": "Open..."
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "open", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 8 3 1"
+ } )
+ add( new FormComponent( "javax.swing.JButton" ) {
+ name: "saveButton"
+ "text": "Save..."
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "save", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 8 3 1"
+ } )
+ add( new FormComponent( "javax.swing.JButton" ) {
+ name: "swingOpenButton"
+ "text": "Swing Open..."
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "swingOpen", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 8 3 1"
+ } )
+ add( new FormComponent( "javax.swing.JButton" ) {
+ name: "swingSaveButton"
+ "text": "Swing Save..."
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "swingSave", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 8 3 1"
+ } )
+ add( new FormComponent( "javax.swing.JButton" ) {
+ name: "awtOpenButton"
+ "text": "AWT Open..."
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "awtOpen", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 8 3 1"
+ } )
+ add( new FormComponent( "javax.swing.JButton" ) {
+ name: "awtSaveButton"
+ "text": "AWT Save..."
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "awtSave", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 8 3 1"
+ } )
+ add( new FormComponent( "javax.swing.JButton" ) {
+ name: "javafxOpenButton"
+ "text": "JavaFX Open..."
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "javafxOpen", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 8 3 1"
+ } )
+ add( new FormComponent( "javax.swing.JButton" ) {
+ name: "javafxSaveButton"
+ "text": "JavaFX Save..."
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "javafxSave", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 8 3 1"
+ } )
+ add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
+ name: "outputScrollPane"
+ add( new FormComponent( "javax.swing.JTextArea" ) {
+ name: "outputField"
+ "rows": 20
+ } )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 9 3 1,growx"
+ } )
+ }, new FormLayoutConstraints( null ) {
+ "location": new java.awt.Point( 0, 0 )
+ "size": new java.awt.Dimension( 825, 465 )
+ } )
+ add( new FormNonVisual( "javax.swing.ButtonGroup" ) {
+ name: "ownerButtonGroup"
+ }, new FormLayoutConstraints( null ) {
+ "location": new java.awt.Point( 0, 475 )
+ } )
+ }
+}
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.java
index 31a55aea..2aa9af1e 100644
--- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.java
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.java
@@ -19,6 +19,7 @@ package com.formdev.flatlaf.testing;
import static com.formdev.flatlaf.ui.FlatNativeWindowsLibrary.*;
import java.awt.Dialog;
import java.awt.EventQueue;
+import java.awt.Font;
import java.awt.SecondaryLoop;
import java.awt.Toolkit;
import java.awt.Window;
@@ -393,6 +394,7 @@ public class FlatSystemFileChooserWindowsTest
//---- pickFoldersCheckBox ----
pickFoldersCheckBox.setText("pickFolders");
+ pickFoldersCheckBox.setFont(pickFoldersCheckBox.getFont().deriveFont(pickFoldersCheckBox.getFont().getStyle() | Font.BOLD));
panel1.add(pickFoldersCheckBox, "cell 0 3");
//---- shareAwareCheckBox ----
@@ -401,6 +403,7 @@ public class FlatSystemFileChooserWindowsTest
//---- forceShowHiddenCheckBox ----
forceShowHiddenCheckBox.setText("forceShowHidden");
+ forceShowHiddenCheckBox.setFont(forceShowHiddenCheckBox.getFont().deriveFont(forceShowHiddenCheckBox.getFont().getStyle() | Font.BOLD));
panel1.add(forceShowHiddenCheckBox, "cell 2 3");
//---- forceFileSystemCheckBox ----
@@ -441,6 +444,7 @@ public class FlatSystemFileChooserWindowsTest
//---- allowMultiSelectCheckBox ----
allowMultiSelectCheckBox.setText("allowMultiSelect");
+ allowMultiSelectCheckBox.setFont(allowMultiSelectCheckBox.getFont().deriveFont(allowMultiSelectCheckBox.getFont().getStyle() | Font.BOLD));
panel1.add(allowMultiSelectCheckBox, "cell 0 7");
//---- hidePinnedPlacesCheckBox ----
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.jfd
index 66590a21..82828444 100644
--- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.jfd
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.jfd
@@ -116,6 +116,7 @@ new FormModel {
add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) {
name: "pickFoldersCheckBox"
"text": "pickFolders"
+ "font": new com.jformdesigner.model.SwingDerivedFont( null, 1, 0, false )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 3"
} )
@@ -128,6 +129,7 @@ new FormModel {
add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) {
name: "forceShowHiddenCheckBox"
"text": "forceShowHidden"
+ "font": new com.jformdesigner.model.SwingDerivedFont( null, 1, 0, false )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 2 3"
} )
@@ -188,6 +190,7 @@ new FormModel {
add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) {
name: "allowMultiSelectCheckBox"
"text": "allowMultiSelect"
+ "font": new com.jformdesigner.model.SwingDerivedFont( null, 1, 0, false )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 7"
} )
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTestFrame.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTestFrame.java
index 520220a9..61b01c1c 100644
--- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTestFrame.java
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTestFrame.java
@@ -242,6 +242,9 @@ public class FlatTestFrame
super.dispose();
FlatUIDefaultsInspector.hide();
+
+ if( getDefaultCloseOperation() == JFrame.EXIT_ON_CLOSE )
+ System.exit( 0 );
}
private void updateTitle() {
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 4d9d326c..6bd75ca2 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -44,6 +44,7 @@ fifesoft-autocomplete = "com.fifesoft:autocomplete:3.3.1"
# flatlaf-testing
glazedlists = "com.glazedlists:glazedlists:1.11.0"
netbeans-api-awt = "org.netbeans.api:org-openide-awt:RELEASE112"
+nativejfilechooser = "li.flor:native-j-file-chooser:1.6.4"
# flatlaf-natives-jna
jna = "net.java.dev.jna:jna:5.15.0"