diff --git a/.github/workflows/natives.yml b/.github/workflows/natives.yml
index cf314dcc..f908574b 100644
--- a/.github/workflows/natives.yml
+++ b/.github/workflows/natives.yml
@@ -32,6 +32,10 @@ jobs:
- uses: gradle/actions/wrapper-validation@v4
+ - name: install libgtk-3-dev
+ if: matrix.os == 'ubuntu'
+ run: sudo apt install libgtk-3-dev
+
- name: Setup Java 11
uses: actions/setup-java@v4
with:
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 952d0ece..353b10b0 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
@@ -34,9 +34,9 @@ import com.formdev.flatlaf.util.SystemInfo;
* @author Karl Tauber
* @since 2.5
*/
-class FlatNativeLinuxLibrary
+public class FlatNativeLinuxLibrary
{
- private static int API_VERSION_LINUX = 3001;
+ private static int API_VERSION_LINUX = 3002;
/**
* Checks whether native library is loaded/available.
@@ -44,7 +44,7 @@ class FlatNativeLinuxLibrary
* Note: It is required to invoke this method before invoking any other
* method of this class. Otherwise, the native library may not be loaded.
*/
- static boolean isLoaded() {
+ public static boolean isLoaded() {
return SystemInfo.isLinux && FlatNativeLibrary.isLoaded( API_VERSION_LINUX );
}
@@ -115,4 +115,48 @@ class FlatNativeLinuxLibrary
return (window instanceof JFrame && JFrame.isDefaultLookAndFeelDecorated() && ((JFrame)window).isUndecorated()) ||
(window instanceof JDialog && JDialog.isDefaultLookAndFeelDecorated() && ((JDialog)window).isUndecorated());
}
+
+
+ /**
+ * https://docs.gtk.org/gtk3/iface.FileChooser.html#properties
+ *
+ * @since 3.6
+ */
+ public static final int
+ FC_select_folder = 1 << 0,
+ FC_select_multiple = 1 << 1,
+ FC_show_hidden = 1 << 2,
+ FC_local_only = 1 << 3, // default
+ FC_do_overwrite_confirmation = 1 << 4, // GTK 3 only; removed and always-on in GTK 4
+ FC_create_folders = 1 << 5; // default for Save
+
+ /**
+ * Shows the Linux system file dialog
+ * GtkFileChooserDialog.
+ *
+ * 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.
+ *
+ * @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}
+ * @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
+ * @param optionsClear options to clear; see {@code FOS_*} constants
+ * @param fileTypeIndex the file type that appears as selected (zero-based)
+ * @param fileTypes file types that the dialog can open or save.
+ * Two or more strings and {@code null} are required for each filter.
+ * First string is the display name of the filter shown in the combobox (e.g. "Text Files").
+ * Subsequent strings are the filter patterns (e.g. "*.txt" or "*").
+ * {@code null} is required to mark end of filter.
+ * @return file path(s) that the user selected; an empty array if canceled;
+ * or {@code null} on failures (no dialog shown)
+ *
+ * @since 3.6
+ */
+ public native static String[] showFileChooser( boolean open,
+ String title, String okButtonLabel, String currentName, String currentFolder,
+ int optionsSet, int optionsClear, int fileTypeIndex, String... fileTypes );
}
diff --git a/flatlaf-natives/flatlaf-natives-linux/README.md b/flatlaf-natives/flatlaf-natives-linux/README.md
index 3dd00c68..0b1cf98b 100644
--- a/flatlaf-natives/flatlaf-natives-linux/README.md
+++ b/flatlaf-natives/flatlaf-natives-linux/README.md
@@ -24,16 +24,25 @@ To build the library on Linux, some packages needs to be installed.
### Ubuntu
`build-essential` contains GCC and development tools. `libxt-dev` contains the
-X11 toolkit development headers.
+X11 toolkit development headers. `libgtk-3-dev` contains the GTK toolkit
+development headers.
~~~
sudo apt update
-sudo apt install build-essential libxt-dev
+sudo apt install build-essential libxt-dev libgtk-3-dev
+~~~
+
+
+### Fedora
+
+~~~
+sudo dnf group install c-development
+sudo dnf install libXt-devel gtk3-devel
~~~
### CentOS
~~~
-sudo yum install libXt-devel
+sudo yum install libXt-devel gtk3-devel
~~~
diff --git a/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts b/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts
index a81d6643..1ae7abbc 100644
--- a/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts
+++ b/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts
@@ -52,7 +52,17 @@ tasks {
includes.from(
"${javaHome}/include",
- "${javaHome}/include/linux"
+ "${javaHome}/include/linux",
+
+ // for GTK
+ "/usr/include/gtk-3.0",
+ "/usr/include/glib-2.0",
+ "/usr/lib/x86_64-linux-gnu/glib-2.0/include",
+ "/usr/include/gdk-pixbuf-2.0",
+ "/usr/include/atk-1.0",
+ "/usr/include/cairo",
+ "/usr/include/pango-1.0",
+ "/usr/include/harfbuzz",
)
compilerArgs.addAll( toolChain.map {
@@ -75,7 +85,7 @@ tasks {
linkerArgs.addAll( toolChain.map {
when( it ) {
- is Gcc, is Clang -> listOf( "-L${jawtPath}", "-l${jawt}" )
+ is Gcc, is Clang -> listOf( "-L${jawtPath}", "-l${jawt}", "-lgtk-3" )
else -> emptyList()
}
} )
diff --git a/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/ApiVersion.cpp b/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/ApiVersion.cpp
index 02454fb3..cdd701a9 100644
--- a/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/ApiVersion.cpp
+++ b/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/ApiVersion.cpp
@@ -24,7 +24,7 @@
// increase this version if changing API or functionality of native library
// also update version in Java class com.formdev.flatlaf.ui.FlatNativeLinuxLibrary
-#define API_VERSION_LINUX 3001
+#define API_VERSION_LINUX 3002
//---- JNI methods ------------------------------------------------------------
diff --git a/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/GtkFileChooser.cpp b/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/GtkFileChooser.cpp
new file mode 100644
index 00000000..ca048af5
--- /dev/null
+++ b/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/GtkFileChooser.cpp
@@ -0,0 +1,177 @@
+/*
+ * 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.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include "com_formdev_flatlaf_ui_FlatNativeLinuxLibrary.h"
+
+/**
+ * @author Karl Tauber
+ * @since 3.6
+ */
+
+//---- class AutoReleaseStringUTF8 --------------------------------------------
+
+class AutoReleaseStringUTF8 {
+ JNIEnv* env;
+ jstring javaString;
+ const char* chars;
+
+public:
+ AutoReleaseStringUTF8( JNIEnv* _env, jstring _javaString ) {
+ env = _env;
+ javaString = _javaString;
+ chars = (javaString != NULL) ? env->GetStringUTFChars( javaString, NULL ) : NULL;
+ }
+ ~AutoReleaseStringUTF8() {
+ if( chars != NULL )
+ env->ReleaseStringUTFChars( javaString, chars );
+ }
+ operator const gchar*() { return chars; }
+};
+
+//---- helper -----------------------------------------------------------------
+
+#define isOptionSet( option ) ((optionsSet & com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_ ## option) != 0)
+#define isOptionClear( option ) ((optionsClear & com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_ ## option) != 0)
+#define isOptionSetOrClear( option ) isOptionSet( option ) || isOptionClear( option )
+
+jobjectArray newJavaStringArray( JNIEnv* env, jsize count ) {
+ jclass stringClass = env->FindClass( "java/lang/String" );
+ return env->NewObjectArray( count, stringClass, NULL );
+}
+
+void initFilters( GtkFileChooser* chooser, JNIEnv* env, jint fileTypeIndex, jobjectArray fileTypes ) {
+ jint length = env->GetArrayLength( fileTypes );
+ if( length <= 0 )
+ return;
+
+ GtkFileFilter* filter = NULL;
+ int filterIndex = 0;
+ for( int i = 0; i < length; i++ ) {
+ jstring jstr = (jstring) env->GetObjectArrayElement( fileTypes, i );
+ if( jstr == NULL ) {
+ if( filter != NULL ) {
+ gtk_file_chooser_add_filter( chooser, filter );
+ if( fileTypeIndex == filterIndex )
+ gtk_file_chooser_set_filter( chooser, filter );
+ filter = NULL;
+ filterIndex++;
+ }
+ continue;
+ }
+
+ AutoReleaseStringUTF8 str( env, jstr );
+ if( filter == NULL ) {
+ filter = gtk_file_filter_new();
+ gtk_file_filter_set_name( filter, str );
+ } else
+ gtk_file_filter_add_pattern( filter, str );
+ }
+}
+
+static void handle_response( GtkWidget* dialog, gint responseId, gpointer data ) {
+ if( responseId == GTK_RESPONSE_ACCEPT )
+ *((GSList**)data) = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER( dialog ) );
+
+ gtk_widget_hide( dialog );
+ gtk_widget_destroy( dialog );
+ gtk_main_quit();
+}
+
+//---- JNI methods ------------------------------------------------------------
+
+extern "C"
+JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_showFileChooser
+ ( JNIEnv* env, jclass cls, jboolean open,
+ jstring title, jstring okButtonLabel, jstring currentName, jstring currentFolder,
+ jint optionsSet, jint optionsClear, jint fileTypeIndex, jobjectArray fileTypes )
+{
+ // initialize GTK
+ if( !gtk_init_check( NULL, NULL ) )
+ return NULL;
+
+ // convert Java strings to C strings
+ AutoReleaseStringUTF8 ctitle( env, title );
+ AutoReleaseStringUTF8 cokButtonLabel( env, okButtonLabel );
+ AutoReleaseStringUTF8 ccurrentName( env, currentName );
+ AutoReleaseStringUTF8 ccurrentFolder( env, currentFolder );
+
+ // create GTK file chooser dialog
+ // https://docs.gtk.org/gtk3/class.FileChooserDialog.html
+ bool selectFolder = isOptionSet( FC_select_folder );
+ 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")),
+ 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,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ (cokButtonLabel != NULL) ? cokButtonLabel : (open ? _("_Open") : _("_Save")), GTK_RESPONSE_ACCEPT,
+ NULL ); // marks end of buttons
+ GtkFileChooser* chooser = GTK_FILE_CHOOSER( dialog );
+
+ if( !open && ccurrentName != NULL )
+ gtk_file_chooser_set_current_name( chooser, ccurrentName );
+ if( ccurrentFolder != NULL )
+ gtk_file_chooser_set_current_folder( chooser, ccurrentFolder );
+
+ if( isOptionSetOrClear( FC_select_multiple ) )
+ gtk_file_chooser_set_select_multiple( chooser, isOptionSet( FC_select_multiple ) );
+ if( isOptionSetOrClear( FC_show_hidden ) )
+ gtk_file_chooser_set_show_hidden( chooser, isOptionSet( FC_show_hidden ) );
+ if( isOptionSetOrClear( FC_local_only ) )
+ gtk_file_chooser_set_local_only( chooser, isOptionSet( FC_local_only ) );
+ if( isOptionSetOrClear( FC_do_overwrite_confirmation ) )
+ gtk_file_chooser_set_do_overwrite_confirmation( chooser, isOptionSet( FC_do_overwrite_confirmation ) );
+ if( isOptionSetOrClear( FC_create_folders ) )
+ gtk_file_chooser_set_create_folders( chooser, isOptionSet( FC_create_folders ) );
+
+ initFilters( chooser, env, fileTypeIndex, fileTypes );
+
+ gtk_window_set_modal( GTK_WINDOW( dialog ), true );
+
+ // show dialog
+ // (similar to what's done in sun_awt_X11_GtkFileDialogPeer.c)
+ GSList* fileList = NULL;
+ g_signal_connect( dialog, "response", G_CALLBACK( handle_response ), &fileList );
+ gtk_widget_show( dialog );
+ gtk_main();
+
+ // canceled?
+ if( fileList == NULL )
+ return newJavaStringArray( env, 0 );
+
+ // convert GSList to Java string array
+ guint count = g_slist_length( fileList );
+ jobjectArray array = newJavaStringArray( env, count );
+ GSList* it = fileList;
+ for( int i = 0; i < count; i++, it = it->next ) {
+ gchar* path = (gchar*) it->data;
+ jstring jpath = env->NewStringUTF( path );
+ g_free( path );
+
+ env->SetObjectArrayElement( array, i, jpath );
+ env->DeleteLocalRef( jpath );
+ }
+ g_slist_free( fileList );
+ return array;
+}
diff --git a/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/X11WmUtils.cpp b/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/X11WmUtils.cpp
index 8cbc57a9..0f01b693 100644
--- a/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/X11WmUtils.cpp
+++ b/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/X11WmUtils.cpp
@@ -36,7 +36,7 @@ Window getWindowHandle( JNIEnv* env, JAWT* awt, jobject window, Display** displa
/**
* Send _NET_WM_MOVERESIZE to window to initiate moving or resizing.
*
- * https://specifications.freedesktop.org/wm-spec/wm-spec-latest.html#idm45446104441728
+ * https://specifications.freedesktop.org/wm-spec/latest/ar01s04.html#id-1.5.4
* https://gitlab.gnome.org/GNOME/gtk/-/blob/main/gdk/x11/gdksurface-x11.c#L3841-3881
*/
extern "C"
diff --git a/flatlaf-natives/flatlaf-natives-linux/src/main/headers/com_formdev_flatlaf_ui_FlatNativeLinuxLibrary.h b/flatlaf-natives/flatlaf-natives-linux/src/main/headers/com_formdev_flatlaf_ui_FlatNativeLinuxLibrary.h
index c3b8c4b2..dd0436a0 100644
--- a/flatlaf-natives/flatlaf-natives-linux/src/main/headers/com_formdev_flatlaf_ui_FlatNativeLinuxLibrary.h
+++ b/flatlaf-natives/flatlaf-natives-linux/src/main/headers/com_formdev_flatlaf_ui_FlatNativeLinuxLibrary.h
@@ -9,6 +9,18 @@ extern "C" {
#endif
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_MOVE
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_MOVE 8L
+#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_select_folder
+#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_select_folder 1L
+#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_select_multiple
+#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_select_multiple 2L
+#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_show_hidden
+#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_show_hidden 4L
+#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_local_only
+#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_local_only 8L
+#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_do_overwrite_confirmation
+#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_do_overwrite_confirmation 16L
+#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_create_folders
+#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_create_folders 32L
/*
* Class: com_formdev_flatlaf_ui_FlatNativeLinuxLibrary
* Method: xMoveOrResizeWindow
@@ -25,6 +37,14 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_xM
JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_xShowWindowMenu
(JNIEnv *, jclass, jobject, jint, jint);
+/*
+ * Class: com_formdev_flatlaf_ui_FlatNativeLinuxLibrary
+ * Method: showFileChooser
+ * Signature: (ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;III[Ljava/lang/String;)[Ljava/lang/String;
+ */
+JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_showFileChooser
+ (JNIEnv *, jclass, jboolean, jstring, jstring, jstring, jstring, jint, jint, jint, jobjectArray);
+
#ifdef __cplusplus
}
#endif
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserLinuxTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserLinuxTest.java
new file mode 100644
index 00000000..ecb0e439
--- /dev/null
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserLinuxTest.java
@@ -0,0 +1,392 @@
+/*
+ * 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 static com.formdev.flatlaf.ui.FlatNativeLinuxLibrary.*;
+import java.awt.EventQueue;
+import java.awt.SecondaryLoop;
+import java.awt.Toolkit;
+import java.awt.Window;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowFocusListener;
+import java.awt.event.WindowListener;
+import java.awt.event.WindowStateListener;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.swing.*;
+import com.formdev.flatlaf.extras.components.*;
+import com.formdev.flatlaf.extras.components.FlatTriStateCheckBox.State;
+import com.formdev.flatlaf.ui.FlatNativeLinuxLibrary;
+import net.miginfocom.swing.*;
+
+/**
+ * @author Karl Tauber
+ */
+public class FlatSystemFileChooserLinuxTest
+ extends FlatTestPanel
+{
+ public static void main( String[] args ) {
+ SwingUtilities.invokeLater( () -> {
+ if( !FlatNativeLinuxLibrary.isLoaded() ) {
+ JOptionPane.showMessageDialog( null, "FlatLaf native library not loaded" );
+ return;
+ }
+
+ FlatTestFrame frame = FlatTestFrame.create( args, "FlatSystemFileChooserLinuxTest" );
+ addListeners( frame );
+ frame.showFrame( FlatSystemFileChooserLinuxTest::new );
+ } );
+ }
+
+ FlatSystemFileChooserLinuxTest() {
+ initComponents();
+
+ fileTypesField.setSelectedItem( null );
+ }
+
+ private void open() {
+ openOrSave( true, false );
+ }
+
+ private void save() {
+ openOrSave( false, false );
+ }
+
+ private void openDirect() {
+ openOrSave( true, true );
+ }
+
+ private void saveDirect() {
+ openOrSave( false, true );
+ }
+
+ private void openOrSave( boolean open, boolean direct ) {
+ String title = n( titleField.getText() );
+ String okButtonLabel = n( okButtonLabelField.getText() );
+ String currentName = n( currentNameField.getText() );
+ String currentFolder = n( currentFolderField.getText() );
+ AtomicInteger optionsSet = new AtomicInteger();
+ AtomicInteger optionsClear = new AtomicInteger();
+
+ o( FC_select_folder, select_folderCheckBox, optionsSet, optionsClear );
+ o( FC_select_multiple, select_multipleCheckBox, optionsSet, optionsClear );
+ o( FC_show_hidden, show_hiddenCheckBox, optionsSet, optionsClear );
+ o( FC_local_only, local_onlyCheckBox, optionsSet, optionsClear );
+ o( FC_do_overwrite_confirmation, do_overwrite_confirmationCheckBox, optionsSet, optionsClear );
+ o( FC_create_folders, create_foldersCheckBox, optionsSet, optionsClear );
+
+ String fileTypesStr = n( (String) fileTypesField.getSelectedItem() );
+ String[] fileTypes = {};
+ if( fileTypesStr != null ) {
+ if( !fileTypesStr.endsWith( ",null" ) )
+ fileTypesStr += ",null";
+ fileTypes = fileTypesStr.trim().split( "[,]+" );
+ for( int i = 0; i < fileTypes.length; i++ ) {
+ if( "null".equals( fileTypes[i] ) )
+ fileTypes[i] = null;
+ }
+ }
+ int fileTypeIndex = fileTypeIndexSlider.getValue();
+
+ if( direct ) {
+ String[] files = FlatNativeLinuxLibrary.showFileChooser( open,
+ title, okButtonLabel, currentName, currentFolder,
+ optionsSet.get(), optionsClear.get(), fileTypeIndex, fileTypes );
+
+ filesField.setText( (files != null) ? Arrays.toString( files ).replace( ',', '\n' ) : "null" );
+ } else {
+ SecondaryLoop secondaryLoop = Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop();
+
+ String[] fileTypes2 = fileTypes;
+ new Thread( () -> {
+ String[] files = FlatNativeLinuxLibrary.showFileChooser( open,
+ title, okButtonLabel, currentName, currentFolder,
+ optionsSet.get(), optionsClear.get(), fileTypeIndex, fileTypes2 );
+
+ System.out.println( " secondaryLoop.exit() returned " + secondaryLoop.exit() );
+
+ EventQueue.invokeLater( () -> {
+ filesField.setText( (files != null) ? Arrays.toString( files ).replace( ',', '\n' ) : "null" );
+ } );
+ } ).start();
+
+ System.out.println( "---- enter secondary loop ----" );
+ System.out.println( "---- secondary loop exited (secondaryLoop.enter() returned " + secondaryLoop.enter() + ") ----" );
+ }
+ }
+
+ private static String n( String s ) {
+ return s != null && !s.isEmpty() ? s : null;
+ }
+
+ private static void o( int option, FlatTriStateCheckBox checkBox, AtomicInteger optionsSet, AtomicInteger optionsClear ) {
+ if( checkBox.getState() == State.SELECTED )
+ optionsSet.set( optionsSet.get() | option );
+ else if( checkBox.getState() == State.UNSELECTED )
+ optionsClear.set( optionsClear.get() | option );
+ }
+
+ 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
+ titleLabel = new JLabel();
+ titleField = new JTextField();
+ panel1 = new JPanel();
+ select_folderCheckBox = new FlatTriStateCheckBox();
+ select_multipleCheckBox = new FlatTriStateCheckBox();
+ do_overwrite_confirmationCheckBox = new FlatTriStateCheckBox();
+ create_foldersCheckBox = new FlatTriStateCheckBox();
+ show_hiddenCheckBox = new FlatTriStateCheckBox();
+ local_onlyCheckBox = new FlatTriStateCheckBox();
+ okButtonLabelLabel = new JLabel();
+ okButtonLabelField = new JTextField();
+ currentNameLabel = new JLabel();
+ currentNameField = new JTextField();
+ currentFolderLabel = new JLabel();
+ currentFolderField = new JTextField();
+ fileTypesLabel = new JLabel();
+ fileTypesField = new JComboBox<>();
+ fileTypeIndexLabel = new JLabel();
+ fileTypeIndexSlider = new JSlider();
+ openButton = new JButton();
+ saveButton = new JButton();
+ openDirectButton = new JButton();
+ saveDirectButton = new JButton();
+ filesScrollPane = new JScrollPane();
+ filesField = new JTextArea();
+
+ //======== this ========
+ setLayout(new MigLayout(
+ "ltr,insets dialog,hidemode 3",
+ // columns
+ "[left]" +
+ "[grow,fill]" +
+ "[fill]",
+ // rows
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[grow,fill]"));
+
+ //---- titleLabel ----
+ titleLabel.setText("title");
+ add(titleLabel, "cell 0 0");
+ add(titleField, "cell 1 0");
+
+ //======== panel1 ========
+ {
+ panel1.setLayout(new MigLayout(
+ "insets 2,hidemode 3",
+ // columns
+ "[left]",
+ // rows
+ "[]0" +
+ "[]0" +
+ "[]0" +
+ "[]0" +
+ "[]0" +
+ "[]"));
+
+ //---- select_folderCheckBox ----
+ select_folderCheckBox.setText("select_folder");
+ select_folderCheckBox.setAllowIndeterminate(false);
+ select_folderCheckBox.setState(FlatTriStateCheckBox.State.UNSELECTED);
+ panel1.add(select_folderCheckBox, "cell 0 0");
+
+ //---- select_multipleCheckBox ----
+ select_multipleCheckBox.setText("select_multiple");
+ select_multipleCheckBox.setState(FlatTriStateCheckBox.State.UNSELECTED);
+ select_multipleCheckBox.setAllowIndeterminate(false);
+ panel1.add(select_multipleCheckBox, "cell 0 1");
+
+ //---- do_overwrite_confirmationCheckBox ----
+ do_overwrite_confirmationCheckBox.setText("do_overwrite_confirmation");
+ panel1.add(do_overwrite_confirmationCheckBox, "cell 0 2");
+
+ //---- create_foldersCheckBox ----
+ create_foldersCheckBox.setText("create_folders");
+ panel1.add(create_foldersCheckBox, "cell 0 3");
+
+ //---- show_hiddenCheckBox ----
+ show_hiddenCheckBox.setText("show_hidden");
+ panel1.add(show_hiddenCheckBox, "cell 0 4");
+
+ //---- local_onlyCheckBox ----
+ local_onlyCheckBox.setText("local_only");
+ panel1.add(local_onlyCheckBox, "cell 0 5");
+ }
+ add(panel1, "cell 2 0 1 6,aligny top,growy 0");
+
+ //---- okButtonLabelLabel ----
+ okButtonLabelLabel.setText("okButtonLabel");
+ add(okButtonLabelLabel, "cell 0 1");
+ add(okButtonLabelField, "cell 1 1");
+
+ //---- currentNameLabel ----
+ currentNameLabel.setText("currentName");
+ add(currentNameLabel, "cell 0 2");
+ add(currentNameField, "cell 1 2");
+
+ //---- currentFolderLabel ----
+ currentFolderLabel.setText("currentFolder");
+ add(currentFolderLabel, "cell 0 3");
+ add(currentFolderField, "cell 1 3");
+
+ //---- fileTypesLabel ----
+ fileTypesLabel.setText("fileTypes");
+ add(fileTypesLabel, "cell 0 4");
+
+ //---- fileTypesField ----
+ fileTypesField.setEditable(true);
+ fileTypesField.setModel(new DefaultComboBoxModel<>(new String[] {
+ "Text Files,*.txt,null",
+ "All Files,*,null",
+ "Text Files,*.txt,null,PDF Files,*.pdf,null,All Files,*,null",
+ "Text and PDF Files,*.txt,*.pdf,null"
+ }));
+ add(fileTypesField, "cell 1 4");
+
+ //---- fileTypeIndexLabel ----
+ fileTypeIndexLabel.setText("fileTypeIndex");
+ add(fileTypeIndexLabel, "cell 0 5");
+
+ //---- fileTypeIndexSlider ----
+ fileTypeIndexSlider.setMaximum(10);
+ fileTypeIndexSlider.setMajorTickSpacing(1);
+ fileTypeIndexSlider.setValue(0);
+ fileTypeIndexSlider.setPaintLabels(true);
+ fileTypeIndexSlider.setSnapToTicks(true);
+ add(fileTypeIndexSlider, "cell 1 5");
+
+ //---- openButton ----
+ openButton.setText("Open...");
+ openButton.addActionListener(e -> open());
+ add(openButton, "cell 0 6 3 1");
+
+ //---- saveButton ----
+ saveButton.setText("Save...");
+ saveButton.addActionListener(e -> save());
+ add(saveButton, "cell 0 6 3 1");
+
+ //---- openDirectButton ----
+ openDirectButton.setText("Open (no-thread)...");
+ openDirectButton.addActionListener(e -> openDirect());
+ add(openDirectButton, "cell 0 6 3 1");
+
+ //---- saveDirectButton ----
+ saveDirectButton.setText("Save (no-thread)...");
+ saveDirectButton.addActionListener(e -> saveDirect());
+ add(saveDirectButton, "cell 0 6 3 1");
+
+ //======== filesScrollPane ========
+ {
+
+ //---- filesField ----
+ filesField.setRows(8);
+ filesScrollPane.setViewportView(filesField);
+ }
+ add(filesScrollPane, "cell 0 7 3 1,growx");
+ // JFormDesigner - End of component initialization //GEN-END:initComponents
+ }
+
+ // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
+ private JLabel titleLabel;
+ private JTextField titleField;
+ private JPanel panel1;
+ private FlatTriStateCheckBox select_folderCheckBox;
+ private FlatTriStateCheckBox select_multipleCheckBox;
+ private FlatTriStateCheckBox do_overwrite_confirmationCheckBox;
+ private FlatTriStateCheckBox create_foldersCheckBox;
+ private FlatTriStateCheckBox show_hiddenCheckBox;
+ private FlatTriStateCheckBox local_onlyCheckBox;
+ private JLabel okButtonLabelLabel;
+ private JTextField okButtonLabelField;
+ private JLabel currentNameLabel;
+ private JTextField currentNameField;
+ private JLabel currentFolderLabel;
+ private JTextField currentFolderField;
+ private JLabel fileTypesLabel;
+ private JComboBox fileTypesField;
+ private JLabel fileTypeIndexLabel;
+ private JSlider fileTypeIndexSlider;
+ private JButton openButton;
+ private JButton saveButton;
+ private JButton openDirectButton;
+ private JButton saveDirectButton;
+ private JScrollPane filesScrollPane;
+ private JTextArea filesField;
+ // JFormDesigner - End of variables declaration //GEN-END:variables
+}
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserLinuxTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserLinuxTest.jfd
new file mode 100644
index 00000000..2c463484
--- /dev/null
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserLinuxTest.jfd
@@ -0,0 +1,182 @@
+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: "titleLabel"
+ "text": "title"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 0"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "titleField"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 0"
+ } )
+ add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
+ "$layoutConstraints": "insets 2,hidemode 3"
+ "$columnConstraints": "[left]"
+ "$rowConstraints": "[]0[]0[]0[]0[]0[]"
+ } ) {
+ name: "panel1"
+ add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) {
+ name: "select_folderCheckBox"
+ "text": "select_folder"
+ "allowIndeterminate": false
+ "state": enum com.formdev.flatlaf.extras.components.FlatTriStateCheckBox$State UNSELECTED
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 0"
+ } )
+ add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) {
+ name: "select_multipleCheckBox"
+ "text": "select_multiple"
+ "state": enum com.formdev.flatlaf.extras.components.FlatTriStateCheckBox$State UNSELECTED
+ "allowIndeterminate": false
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 1"
+ } )
+ add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) {
+ name: "do_overwrite_confirmationCheckBox"
+ "text": "do_overwrite_confirmation"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 2"
+ } )
+ add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) {
+ name: "create_foldersCheckBox"
+ "text": "create_folders"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 3"
+ } )
+ add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) {
+ name: "show_hiddenCheckBox"
+ "text": "show_hidden"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 4"
+ } )
+ add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) {
+ name: "local_onlyCheckBox"
+ "text": "local_only"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 5"
+ } )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 2 0 1 6,aligny top,growy 0"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "okButtonLabelLabel"
+ "text": "okButtonLabel"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 1"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "okButtonLabelField"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 1"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "currentNameLabel"
+ "text": "currentName"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 2"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "currentNameField"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 2"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "currentFolderLabel"
+ "text": "currentFolder"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 3"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "currentFolderField"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 3"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "fileTypesLabel"
+ "text": "fileTypes"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 4"
+ } )
+ add( new FormComponent( "javax.swing.JComboBox" ) {
+ name: "fileTypesField"
+ "editable": true
+ "model": new javax.swing.DefaultComboBoxModel {
+ selectedItem: "Text Files,*.txt,null"
+ addElement( "Text Files,*.txt,null" )
+ addElement( "All Files,*,null" )
+ addElement( "Text Files,*.txt,null,PDF Files,*.pdf,null,All Files,*,null" )
+ addElement( "Text and PDF Files,*.txt,*.pdf,null" )
+ }
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 4"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "fileTypeIndexLabel"
+ "text": "fileTypeIndex"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 5"
+ } )
+ 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 5"
+ } )
+ 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 6 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 6 3 1"
+ } )
+ add( new FormComponent( "javax.swing.JButton" ) {
+ name: "openDirectButton"
+ "text": "Open (no-thread)..."
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "openDirect", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 6 3 1"
+ } )
+ add( new FormComponent( "javax.swing.JButton" ) {
+ name: "saveDirectButton"
+ "text": "Save (no-thread)..."
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "saveDirect", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 6 3 1"
+ } )
+ add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
+ name: "filesScrollPane"
+ add( new FormComponent( "javax.swing.JTextArea" ) {
+ name: "filesField"
+ "rows": 8
+ } )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 7 3 1,growx"
+ } )
+ }, new FormLayoutConstraints( null ) {
+ "location": new java.awt.Point( 0, 0 )
+ "size": new java.awt.Dimension( 690, 630 )
+ } )
+ }
+}