System File Chooser: implemented native bindings for GtkFileChooserDialog on Linux

This commit is contained in:
Karl Tauber
2025-01-03 16:22:07 +01:00
parent 63272a03cf
commit 2b810addd8
10 changed files with 848 additions and 10 deletions

View File

@@ -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
~~~

View File

@@ -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()
}
} )

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

View File

@@ -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 <jawt.h>
#include <linux/jawt_md.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <glib/gi18n.h>
#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;
}

View File

@@ -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"

View File

@@ -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