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
{
private static int API_VERSION_LINUX = 3002;
private static int API_VERSION_LINUX = 3003;
/**
* Checks whether native library is loaded/available.
@@ -178,6 +178,7 @@ public class FlatNativeLinuxLibrary
* to avoid blocking the AWT event dispatching thread.
*
* @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 title text displayed in dialog title; or {@code null}
* @param okButtonLabel text displayed in default button; or {@code null}.
@@ -199,7 +200,7 @@ public class FlatNativeLinuxLibrary
*
* @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,
int optionsSet, int optionsClear, FileChooserCallback callback,
int fileTypeIndex, String... fileTypes );

View File

@@ -22,9 +22,10 @@ import java.awt.SecondaryLoop;
import java.awt.Toolkit;
import java.awt.Window;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@@ -590,7 +591,7 @@ public class SystemFileChooser
if( filters.size() == 1 &&
filters.get( 0 ) == getAcceptAllFileFilter() &&
(fileFilter == getAcceptAllFileFilter() || fileFilter == null) )
return Collections.emptyList();
return new ArrayList<>();
// check whether current filter is already in list
if( (fileFilter != null && filters.contains( fileFilter )) || fileFilter == null )
@@ -1073,8 +1074,18 @@ public class SystemFileChooser
private static class LinuxFileChooserProvider
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
String[] showSystemDialog( Window owner, SystemFileChooser fc ) {
int dark = FlatLaf.isLafDark() ? 1 : 0;
boolean open = (fc.getDialogType() == OPEN_DIALOG);
String approveButtonText = fc.getApproveButtonText();
int approveButtonMnemonic = fc.getApproveButtonMnemonic();
@@ -1152,7 +1163,7 @@ public class SystemFileChooser
} : null;
// show system file dialog
return FlatNativeLinuxLibrary.showFileChooser( owner, open,
return FlatNativeLinuxLibrary.showFileChooser( owner, dark, open,
fc.getDialogTitle(), approveButtonText, currentName, currentFolder,
optionsSet, optionsClear, callback,
fileTypeIndex, fileTypes.toArray( new String[fileTypes.size()] ) );
@@ -1175,6 +1186,41 @@ public class SystemFileChooser
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 ----
private static class LinuxApproveContext

View File

@@ -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 3002
#define API_VERSION_LINUX 3003
//---- JNI methods ------------------------------------------------------------

View File

@@ -168,7 +168,7 @@ static void handle_response( GtkWidget* dialog, gint responseId, gpointer data )
extern "C"
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,
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 );
// 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
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

View File

@@ -7,6 +7,22 @@
#ifdef __cplusplus
extern "C" {
#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
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_MOVE 8L
#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
* 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
(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

View File

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

View File

@@ -77,7 +77,7 @@ public class FlatSystemFileChooserTest
FlatSystemFileChooserTest() {
initComponents();
if( !NativeJFileChooser.FX_AVAILABLE ) {
if( SystemInfo.isLinux || !NativeJFileChooser.FX_AVAILABLE ) {
javafxOpenButton.setEnabled( false );
javafxSaveButton.setEnabled( false );
}
@@ -231,7 +231,7 @@ public class FlatSystemFileChooserTest
int fileTypeIndex = fileTypeIndexSlider.getValue();
if( !useAcceptAllFileFilterCheckBox.isSelected() )
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.getAcceptAllFileFilter()
: new SystemFileChooser.FileNameExtensionFilter( fileTypes[i], fileTypes[i+1].split( ";" ) ) );
@@ -282,7 +282,7 @@ public class FlatSystemFileChooserTest
int fileTypeIndex = fileTypeIndexSlider.getValue();
if( !useAcceptAllFileFilterCheckBox.isSelected() )
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.getAcceptAllFileFilter()
: new FileNameExtensionFilter( fileTypes[i], fileTypes[i+1].split( ";" ) ) );