Compare commits

..

2 Commits

Author SHA1 Message Date
Karl Tauber
9e8b8697d1 System File Chooser: Linux: show file dialog in dark if current FlatLaf theme is dark (PR #988)
Some checks failed
CI / build (push) Has been cancelled
CI / release (push) Has been cancelled
Error Prone / error-prone (push) Has been cancelled
Native Libraries / Natives (macos-latest) (push) Has been cancelled
Native Libraries / Natives (ubuntu-24.04-arm) (push) Has been cancelled
Native Libraries / Natives (ubuntu-latest) (push) Has been cancelled
Native Libraries / Natives (windows-latest) (push) Has been cancelled
2025-12-02 14:11:10 +01:00
Karl Tauber
58e073a05b System File Chooser: on Linux when JavaFX is used in application, then always use Swing file chooser because system file dialog does work (PR #988)
with Java 8, some GLib related messages are logged to console and system file dialog is not shown
e.g.: `GLib-GObject-WARNING **: cannot register existing type 'GdkDisplayManager'`

with Java 17, the system file dialog is shown, but then JavaFX no longer works
with Java 21, the application quits/crashes immediately when trying to show system file dialog

also fixed Error Prone warning in `getFiltersForDialog()`
2025-12-01 20:10:06 +01:00
7 changed files with 110 additions and 15 deletions

View File

@@ -37,7 +37,7 @@ import com.formdev.flatlaf.util.SystemInfo;
*/ */
public class FlatNativeLinuxLibrary public class FlatNativeLinuxLibrary
{ {
private static int API_VERSION_LINUX = 3002; private static int API_VERSION_LINUX = 3003;
/** /**
* Checks whether native library is loaded/available. * Checks whether native library is loaded/available.
@@ -178,6 +178,7 @@ public class FlatNativeLinuxLibrary
* to avoid blocking the AWT event dispatching thread. * to avoid blocking the AWT event dispatching thread.
* *
* @param owner the owner of the file dialog; or {@code null} * @param owner the owner of the file dialog; or {@code null}
* @param dark preferred appearance of the file dialog: {@code 1} = prefer dark, {@code 0} = prefer light, {@code -1} = default
* @param open if {@code true}, shows the open dialog; if {@code false}, shows the save dialog * @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 title text displayed in dialog title; or {@code null}
* @param okButtonLabel text displayed in default button; or {@code null}. * @param okButtonLabel text displayed in default button; or {@code null}.
@@ -199,7 +200,7 @@ public class FlatNativeLinuxLibrary
* *
* @since 3.7 * @since 3.7
*/ */
public native static String[] showFileChooser( Window owner, boolean open, public native static String[] showFileChooser( Window owner, int dark, boolean open,
String title, String okButtonLabel, String currentName, String currentFolder, String title, String okButtonLabel, String currentName, String currentFolder,
int optionsSet, int optionsClear, FileChooserCallback callback, int optionsSet, int optionsClear, FileChooserCallback callback,
int fileTypeIndex, String... fileTypes ); int fileTypeIndex, String... fileTypes );

View File

@@ -22,9 +22,10 @@ import java.awt.SecondaryLoop;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.awt.Window; import java.awt.Window;
import java.io.File; import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@@ -590,7 +591,7 @@ public class SystemFileChooser
if( filters.size() == 1 && if( filters.size() == 1 &&
filters.get( 0 ) == getAcceptAllFileFilter() && filters.get( 0 ) == getAcceptAllFileFilter() &&
(fileFilter == getAcceptAllFileFilter() || fileFilter == null) ) (fileFilter == getAcceptAllFileFilter() || fileFilter == null) )
return Collections.emptyList(); return new ArrayList<>();
// check whether current filter is already in list // check whether current filter is already in list
if( (fileFilter != null && filters.contains( fileFilter )) || fileFilter == null ) if( (fileFilter != null && filters.contains( fileFilter )) || fileFilter == null )
@@ -1073,8 +1074,18 @@ public class SystemFileChooser
private static class LinuxFileChooserProvider private static class LinuxFileChooserProvider
extends SystemFileChooserProvider extends SystemFileChooserProvider
{ {
@Override
public File[] showDialog( Window owner, SystemFileChooser fc ) {
// fallback to Swing file chooser if JavaFX is initialized
if( isFXinitialized() )
return new SwingFileChooserProvider().showDialog( owner, fc );
return super.showDialog( owner, fc );
}
@Override @Override
String[] showSystemDialog( Window owner, SystemFileChooser fc ) { String[] showSystemDialog( Window owner, SystemFileChooser fc ) {
int dark = FlatLaf.isLafDark() ? 1 : 0;
boolean open = (fc.getDialogType() == OPEN_DIALOG); boolean open = (fc.getDialogType() == OPEN_DIALOG);
String approveButtonText = fc.getApproveButtonText(); String approveButtonText = fc.getApproveButtonText();
int approveButtonMnemonic = fc.getApproveButtonMnemonic(); int approveButtonMnemonic = fc.getApproveButtonMnemonic();
@@ -1152,7 +1163,7 @@ public class SystemFileChooser
} : null; } : null;
// show system file dialog // show system file dialog
return FlatNativeLinuxLibrary.showFileChooser( owner, open, return FlatNativeLinuxLibrary.showFileChooser( owner, dark, open,
fc.getDialogTitle(), approveButtonText, currentName, currentFolder, fc.getDialogTitle(), approveButtonText, currentName, currentFolder,
optionsSet, optionsClear, callback, optionsSet, optionsClear, callback,
fileTypeIndex, fileTypes.toArray( new String[fileTypes.size()] ) ); fileTypeIndex, fileTypes.toArray( new String[fileTypes.size()] ) );
@@ -1175,6 +1186,41 @@ public class SystemFileChooser
return buf.toString(); return buf.toString();
} }
private static Boolean fxinitialized;
boolean isFXinitialized() {
if( fxinitialized != null )
return fxinitialized;
// check whether JavaFX is available
try {
Class.forName( "javafx.application.Platform", false, getClass().getClassLoader() );
} catch( ClassNotFoundException ex ) {
// JavaFX is not available
fxinitialized = false;
return fxinitialized;
}
// check whether JavaFX is initialized
try {
Class<?> cls = Class.forName( "javafx.application.Platform" );
Method m = cls.getMethod( "runLater", Runnable.class );
m.invoke( null, (Runnable) () -> {} );
fxinitialized = true;
return fxinitialized;
} catch( InvocationTargetException ex ) {
if( ex.getCause() instanceof IllegalStateException )
return false; // JavaFX is available, but not (yet) initialized
} catch( Throwable ex ) {
// ignore
}
// other error --> assume that JavaFX is not initialized
fxinitialized = false;
return fxinitialized;
}
//---- class LinuxApproveContext ---- //---- class LinuxApproveContext ----
private static class LinuxApproveContext private static class LinuxApproveContext

View File

@@ -24,7 +24,7 @@
// increase this version if changing API or functionality of native library // increase this version if changing API or functionality of native library
// also update version in Java class com.formdev.flatlaf.ui.FlatNativeLinuxLibrary // also update version in Java class com.formdev.flatlaf.ui.FlatNativeLinuxLibrary
#define API_VERSION_LINUX 3002 #define API_VERSION_LINUX 3003
//---- JNI methods ------------------------------------------------------------ //---- JNI methods ------------------------------------------------------------

View File

@@ -168,7 +168,7 @@ static void handle_response( GtkWidget* dialog, gint responseId, gpointer data )
extern "C" extern "C"
JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_showFileChooser JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_showFileChooser
( JNIEnv* env, jclass cls, jobject owner, jboolean open, ( JNIEnv* env, jclass cls, jobject owner, jint dark, jboolean open,
jstring title, jstring okButtonLabel, jstring currentName, jstring currentFolder, jstring title, jstring okButtonLabel, jstring currentName, jstring currentFolder,
jint optionsSet, jint optionsClear, jobject callback, jint fileTypeIndex, jobjectArray fileTypes ) jint optionsSet, jint optionsClear, jobject callback, jint fileTypeIndex, jobjectArray fileTypes )
{ {
@@ -226,10 +226,40 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrar
gtk_window_set_modal( GTK_WINDOW( dialog ), true ); gtk_window_set_modal( GTK_WINDOW( dialog ), true );
// file dialog should use same screen as owner // file dialog should use same screen as owner
gtk_window_set_screen( GTK_WINDOW( dialog ), gdk_window_get_screen( gdkOwner ) ); GdkScreen* screen = gdk_window_get_screen( gdkOwner );
gtk_window_set_screen( GTK_WINDOW( dialog ), screen );
// set the transient when the file dialog is realized // set the transient when the file dialog is realized
g_signal_connect( dialog, "realize", G_CALLBACK( handle_realize ), gdkOwner ); g_signal_connect( dialog, "realize", G_CALLBACK( handle_realize ), gdkOwner );
// set light/dark appearance
if( dark >= 0 ) {
GtkSettings *settings = gtk_settings_get_for_screen( screen );
// get current GTK theme
gchar* currentGtkTheme;
g_object_get( settings, "gtk-theme-name", &currentGtkTheme, NULL );
const char* darkSuffix = "-dark";
bool isDarkGtkTheme = g_str_has_suffix( currentGtkTheme, darkSuffix );
if( isDarkGtkTheme && dark == 0 ) {
// current GTK theme is dark, but FlatLaf theme is light
// in this case, "gtk-application-prefer-dark-theme" does not work
// and there is no "gtk-application-prefer-light-theme" setting
// --> try to switch to light GTK theme (if available)
gchar* lightGtkTheme = g_strndup( currentGtkTheme, strlen( currentGtkTheme ) - strlen( darkSuffix ) );
gchar* themeDir = g_strdup_printf( "/usr/share/themes/%s", lightGtkTheme );
if( g_file_test( themeDir, G_FILE_TEST_IS_DIR ) )
g_object_set( settings, "gtk-theme-name", lightGtkTheme, NULL );
g_free( themeDir );
g_free( lightGtkTheme );
}
g_free( currentGtkTheme );
// let GTK know whether we prefer a dark theme
g_object_set( settings, "gtk-application-prefer-dark-theme", (dark == 1), NULL );
}
} }
// show dialog // show dialog

View File

@@ -7,6 +7,22 @@
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_TOPLEFT
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_TOPLEFT 0L
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_TOP
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_TOP 1L
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_TOPRIGHT
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_TOPRIGHT 2L
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_RIGHT
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_RIGHT 3L
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_BOTTOMRIGHT
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_BOTTOMRIGHT 4L
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_BOTTOM
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_BOTTOM 5L
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_BOTTOMLEFT
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_BOTTOMLEFT 6L
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_LEFT
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_LEFT 7L
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_MOVE #undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_MOVE
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_MOVE 8L #define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_MOVE 8L
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_select_folder #undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_select_folder
@@ -48,10 +64,10 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_is
/* /*
* Class: com_formdev_flatlaf_ui_FlatNativeLinuxLibrary * Class: com_formdev_flatlaf_ui_FlatNativeLinuxLibrary
* Method: showFileChooser * Method: showFileChooser
* Signature: (Ljava/awt/Window;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILcom/formdev/flatlaf/ui/FlatNativeLinuxLibrary/FileChooserCallback;I[Ljava/lang/String;)[Ljava/lang/String; * Signature: (Ljava/awt/Window;IZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILcom/formdev/flatlaf/ui/FlatNativeLinuxLibrary/FileChooserCallback;I[Ljava/lang/String;)[Ljava/lang/String;
*/ */
JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_showFileChooser JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_showFileChooser
(JNIEnv *, jclass, jobject, jboolean, jstring, jstring, jstring, jstring, jint, jint, jobject, jint, jobjectArray); (JNIEnv *, jclass, jobject, jint, jboolean, jstring, jstring, jstring, jstring, jint, jint, jobject, jint, jobjectArray);
/* /*
* Class: com_formdev_flatlaf_ui_FlatNativeLinuxLibrary * Class: com_formdev_flatlaf_ui_FlatNativeLinuxLibrary

View File

@@ -24,6 +24,7 @@ import java.awt.Window;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.*; import javax.swing.*;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.extras.components.*; import com.formdev.flatlaf.extras.components.*;
import com.formdev.flatlaf.extras.components.FlatTriStateCheckBox.State; import com.formdev.flatlaf.extras.components.FlatTriStateCheckBox.State;
import com.formdev.flatlaf.testing.FlatSystemFileChooserTest.DummyModalDialog; import com.formdev.flatlaf.testing.FlatSystemFileChooserTest.DummyModalDialog;
@@ -121,8 +122,9 @@ public class FlatSystemFileChooserLinuxTest
System.out.println( FlatNativeLinuxLibrary.isGtk3Available() ); System.out.println( FlatNativeLinuxLibrary.isGtk3Available() );
int dark = FlatLaf.isLafDark() ? 1 : 0;
if( direct ) { if( direct ) {
String[] files = FlatNativeLinuxLibrary.showFileChooser( owner, open, String[] files = FlatNativeLinuxLibrary.showFileChooser( owner, dark, open,
title, okButtonLabel, currentName, currentFolder, title, okButtonLabel, currentName, currentFolder,
optionsSet.get(), optionsClear.get(), callback, fileTypeIndex, fileTypes ); optionsSet.get(), optionsClear.get(), callback, fileTypeIndex, fileTypes );
@@ -132,7 +134,7 @@ public class FlatSystemFileChooserLinuxTest
String[] fileTypes2 = fileTypes; String[] fileTypes2 = fileTypes;
new Thread( () -> { new Thread( () -> {
String[] files = FlatNativeLinuxLibrary.showFileChooser( owner, open, String[] files = FlatNativeLinuxLibrary.showFileChooser( owner, dark, open,
title, okButtonLabel, currentName, currentFolder, title, okButtonLabel, currentName, currentFolder,
optionsSet.get(), optionsClear.get(), callback, fileTypeIndex, fileTypes2 ); optionsSet.get(), optionsClear.get(), callback, fileTypeIndex, fileTypes2 );

View File

@@ -77,7 +77,7 @@ public class FlatSystemFileChooserTest
FlatSystemFileChooserTest() { FlatSystemFileChooserTest() {
initComponents(); initComponents();
if( !NativeJFileChooser.FX_AVAILABLE ) { if( SystemInfo.isLinux || !NativeJFileChooser.FX_AVAILABLE ) {
javafxOpenButton.setEnabled( false ); javafxOpenButton.setEnabled( false );
javafxSaveButton.setEnabled( false ); javafxSaveButton.setEnabled( false );
} }
@@ -231,7 +231,7 @@ public class FlatSystemFileChooserTest
int fileTypeIndex = fileTypeIndexSlider.getValue(); int fileTypeIndex = fileTypeIndexSlider.getValue();
if( !useAcceptAllFileFilterCheckBox.isSelected() ) if( !useAcceptAllFileFilterCheckBox.isSelected() )
fc.setAcceptAllFileFilterUsed( false ); fc.setAcceptAllFileFilterUsed( false );
for( int i = 0; i < fileTypes.length; i += 2 ) { for( int i = 0; i < fileTypes.length; i += 2 ) {
fc.addChoosableFileFilter( "*".equals( fileTypes[i+1] ) fc.addChoosableFileFilter( "*".equals( fileTypes[i+1] )
? fc.getAcceptAllFileFilter() ? fc.getAcceptAllFileFilter()
: new SystemFileChooser.FileNameExtensionFilter( fileTypes[i], fileTypes[i+1].split( ";" ) ) ); : new SystemFileChooser.FileNameExtensionFilter( fileTypes[i], fileTypes[i+1].split( ";" ) ) );
@@ -282,7 +282,7 @@ public class FlatSystemFileChooserTest
int fileTypeIndex = fileTypeIndexSlider.getValue(); int fileTypeIndex = fileTypeIndexSlider.getValue();
if( !useAcceptAllFileFilterCheckBox.isSelected() ) if( !useAcceptAllFileFilterCheckBox.isSelected() )
fc.setAcceptAllFileFilterUsed( false ); fc.setAcceptAllFileFilterUsed( false );
for( int i = 0; i < fileTypes.length; i += 2 ) { for( int i = 0; i < fileTypes.length; i += 2 ) {
fc.addChoosableFileFilter( "*".equals( fileTypes[i+1] ) fc.addChoosableFileFilter( "*".equals( fileTypes[i+1] )
? fc.getAcceptAllFileFilter() ? fc.getAcceptAllFileFilter()
: new FileNameExtensionFilter( fileTypes[i], fileTypes[i+1].split( ";" ) ) ); : new FileNameExtensionFilter( fileTypes[i], fileTypes[i+1].split( ";" ) ) );