Merge PR #988: System File Chooser

This commit is contained in:
Karl Tauber
2025-10-27 19:16:50 +01:00
56 changed files with 8559 additions and 76 deletions

View File

@@ -25,6 +25,7 @@ To build the library on Linux, some packages needs to be installed:
- `build-essential` - GCC and development tools
- `libxt-dev` - X11 toolkit development headers
- `libgtk-3-dev` - GTK 3 toolkit development headers
- `g++-aarch64-linux-gnu` - GNU C++ compiler for the arm64 architecture (only on
x86_64 Linux for cross-compiling for arm64 architecture)
@@ -32,19 +33,39 @@ To build the library on Linux, some packages needs to be installed:
### Ubuntu
~~~
sudo apt update
sudo apt install build-essential libxt-dev
sudo apt-get update
sudo apt-get install build-essential libxt-dev libgtk-3-dev
~~~
Only on x86_64 Linux for cross-compiling for arm64 architecture:
#### Cross-compile for arm64 architecture on x86_64 Linux
Only needed on x86_64 Linux if you want cross-compile for arm64 architecture:
~~~
sudo apt install g++-aarch64-linux-gnu
sudo apt-get install g++-aarch64-linux-gnu
~~~
Download `libgtk-3.so` for arm64 architecture:
~~~
cd flatlaf-natives/flatlaf-natives-linux/lib/aarch64
wget --no-verbose https://ports.ubuntu.com/pool/main/g/gtk%2b3.0/libgtk-3-0_3.24.18-1ubuntu1_arm64.deb
ar -x libgtk-3-0_3.24.18-1ubuntu1_arm64.deb data.tar.xz
tar -xvf data.tar.xz --wildcards --to-stdout "./usr/lib/aarch64-linux-gnu/libgtk-3.so.0.*" > libgtk-3.so
rm libgtk-3-0_3.24.18-1ubuntu1_arm64.deb data.tar.xz
~~~
### 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

@@ -65,15 +65,37 @@ tasks {
includes.from(
"${javaHome}/include",
"${javaHome}/include/linux"
"${javaHome}/include/linux",
// for GTK
"/usr/include/gtk-3.0",
"/usr/include/glib-2.0",
if( name.contains( "X86-64" ) ) "/usr/lib/x86_64-linux-gnu/glib-2.0/include"
else "/usr/lib/aarch64-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 {
when( it ) {
is Gcc, is Clang -> listOf()
is Gcc, is Clang -> listOf( "-fvisibility=hidden" )
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<LinkSharedLibrary>().configureEach {
@@ -88,7 +110,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()
}
} )
@@ -128,7 +150,20 @@ tasks {
"-I", "${javaHome}/include/linux",
"-I", "$include",
// for GTK
"-I", "/usr/include/gtk-3.0",
"-I", "/usr/include/glib-2.0",
"-I", "/usr/lib/x86_64-linux-gnu/glib-2.0/include",
"-I", "/usr/include/gdk-pixbuf-2.0",
"-I", "/usr/include/atk-1.0",
"-I", "/usr/include/cairo",
"-I", "/usr/include/pango-1.0",
"-I", "/usr/include/harfbuzz",
"$src/ApiVersion.cpp",
"$src/GtkFileChooser.cpp",
"$src/GtkMessageDialog.cpp",
"$src/JNIUtils.cpp",
"$src/X11WmUtils.cpp",
)
}
@@ -152,10 +187,15 @@ tasks {
"-o", "$outDir/$libraryName",
"$objDir/ApiVersion.o",
"$objDir/GtkFileChooser.o",
"$objDir/GtkMessageDialog.o",
"$objDir/JNIUtils.o",
"$objDir/X11WmUtils.o",
"-lstdc++",
"-L${layout.projectDirectory}/lib/aarch64",
"-ljawt",
"-lgtk-3",
)
doLast {

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,277 @@
/*
* 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 "JNIUtils.h"
#include "com_formdev_flatlaf_ui_FlatNativeLinuxLibrary.h"
/**
* @author Karl Tauber
* @since 3.7
*/
// declare external methods
extern Window getWindowHandle( JNIEnv* env, JAWT* awt, jobject window, Display** display_return );
// declare internal methods
static jobjectArray fileListToStringArray( JNIEnv* env, GSList* fileList );
//---- 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 )
static jobjectArray newJavaStringArray( JNIEnv* env, jsize count ) {
jclass stringClass = env->FindClass( "java/lang/String" );
return env->NewObjectArray( count, stringClass, NULL );
}
static 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 GdkWindow* getGdkWindow( JNIEnv* env, jobject window ) {
// get the AWT
JAWT awt;
awt.version = JAWT_VERSION_1_4;
if( !JAWT_GetAWT( env, &awt ) )
return NULL;
// get Xlib window and display from AWT window
Display* display;
Window w = getWindowHandle( env, &awt, window, &display );
if( w == 0 )
return NULL;
// based on GetAllocNativeWindowHandle() from https://github.com/btzy/nativefiledialog-extended
// https://github.com/btzy/nativefiledialog-extended/blob/29e3bcb578345b9fa345d1d7683f00c150565ca3/src/nfd_gtk.cpp#L384-L437
GdkDisplay* gdkDisplay = gdk_x11_lookup_xdisplay( display );
if( gdkDisplay == NULL ) {
// search for existing X11 display (there should only be one, even if multiple screens are connected)
GdkDisplayManager* displayManager = gdk_display_manager_get();
GSList* displays = gdk_display_manager_list_displays( displayManager );
for( GSList* l = displays; l; l = l->next ) {
if( GDK_IS_X11_DISPLAY( l->data ) ) {
gdkDisplay = GDK_DISPLAY( l->data );
break;
}
}
g_slist_free( displays );
// create our own X11 display
if( gdkDisplay == NULL ) {
gdk_set_allowed_backends( "x11" );
gdkDisplay = gdk_display_manager_open_display( displayManager, NULL );
gdk_set_allowed_backends( NULL );
if( gdkDisplay == NULL )
return NULL;
}
}
return gdk_x11_window_foreign_new_for_display( gdkDisplay, w );
}
static void handle_realize( GtkWidget* dialog, gpointer data ) {
GdkWindow* gdkOwner = static_cast<GdkWindow*>( data );
// make file dialog a transient of owner window,
// which centers file dialog on owner and keeps file dialog above owner
gdk_window_set_transient_for( gtk_widget_get_window( dialog ), gdkOwner );
// necessary because gdk_x11_window_foreign_new_for_display() increases the reference counter
g_object_unref( gdkOwner );
}
struct ResponseData {
JNIEnv* env;
jobject callback;
GSList* fileList;
ResponseData( JNIEnv* _env, jobject _callback ) {
env = _env;
callback = _callback;
fileList = NULL;
}
};
static void handle_response( GtkWidget* dialog, gint responseId, gpointer data ) {
// get filenames if user pressed OK
if( responseId == GTK_RESPONSE_ACCEPT ) {
ResponseData *response = static_cast<ResponseData*>( data );
if( response->callback != NULL ) {
GSList* fileList = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER( dialog ) );
jobjectArray files = fileListToStringArray( response->env, fileList );
GtkWindow* window = GTK_WINDOW( dialog );
// invoke callback: boolean approve( String[] files, long hwnd );
jclass cls = response->env->GetObjectClass( response->callback );
jmethodID approveID = response->env->GetMethodID( cls, "approve", "([Ljava/lang/String;J)Z" );
if( approveID != NULL && !response->env->CallBooleanMethod( response->callback, approveID, files, window ) )
return; // keep dialog open
}
response->fileList = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER( dialog ) );
}
// hide/destroy file dialog and quit loop
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, jobject owner, jboolean open,
jstring title, jstring okButtonLabel, jstring currentName, jstring currentFolder,
jint optionsSet, jint optionsClear, jobject callback, 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
: (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
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
: (selectFolder ? _("_Select") : (open ? _("_Open") : _("_Save"))), GTK_RESPONSE_ACCEPT,
NULL ); // marks end of buttons
GtkFileChooser* chooser = GTK_FILE_CHOOSER( dialog );
// set current name and folder
if( !open && ccurrentName != NULL )
gtk_file_chooser_set_current_name( chooser, ccurrentName );
if( ccurrentFolder != NULL )
gtk_file_chooser_set_current_folder( chooser, ccurrentFolder );
// set options
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 ) );
// initialize filter
initFilters( chooser, env, fileTypeIndex, fileTypes );
// setup modality
GdkWindow* gdkOwner = (owner != NULL) ? getGdkWindow( env, owner ) : NULL;
if( gdkOwner != NULL ) {
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 ) );
// set the transient when the file dialog is realized
g_signal_connect( dialog, "realize", G_CALLBACK( handle_realize ), gdkOwner );
}
// show dialog
// (similar to what's done in sun_awt_X11_GtkFileDialogPeer.c)
ResponseData responseData( env, callback );
g_signal_connect( dialog, "response", G_CALLBACK( handle_response ), &responseData );
gtk_widget_show( dialog );
// necessary to bring file dialog to the front (and make it active)
// see issues:
// https://github.com/btzy/nativefiledialog-extended/issues/31
// https://github.com/mlabbe/nativefiledialog/pull/92
// https://github.com/guillaumechereau/noc/pull/11
if( GDK_IS_X11_DISPLAY( gtk_widget_get_display( GTK_WIDGET( dialog ) ) ) ) {
GdkWindow* gdkWindow = gtk_widget_get_window( GTK_WIDGET( dialog ) );
gdk_window_set_events( gdkWindow, static_cast<GdkEventMask>( gdk_window_get_events( gdkWindow ) | GDK_PROPERTY_CHANGE_MASK ) );
gtk_window_present_with_time( GTK_WINDOW( dialog ), gdk_x11_get_server_time( gdkWindow ) );
}
// start event loop (will be quit in respone handler)
gtk_main();
// canceled?
if( responseData.fileList == NULL )
return newJavaStringArray( env, 0 );
// convert GSList to Java string array
return fileListToStringArray( env, responseData.fileList );
}
static jobjectArray fileListToStringArray( JNIEnv* env, GSList* fileList ) {
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

@@ -0,0 +1,76 @@
/*
* 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 "JNIUtils.h"
#include "com_formdev_flatlaf_ui_FlatNativeLinuxLibrary.h"
/**
* @author Karl Tauber
* @since 3.7
*/
extern "C"
JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_showMessageDialog
( JNIEnv* env, jclass cls, jlong hwndParent, jint messageType, jstring primaryText, jstring secondaryText,
jint defaultButton, jobjectArray buttons )
{
GtkWindow* window = (GtkWindow*) hwndParent;
// convert message type
GtkMessageType gmessageType;
switch( messageType ) {
case /* JOptionPane.ERROR_MESSAGE */ 0: gmessageType = GTK_MESSAGE_ERROR; break;
case /* JOptionPane.INFORMATION_MESSAGE */ 1: gmessageType = GTK_MESSAGE_INFO; break;
case /* JOptionPane.WARNING_MESSAGE */ 2: gmessageType = GTK_MESSAGE_WARNING; break;
case /* JOptionPane.QUESTION_MESSAGE */ 3: gmessageType = GTK_MESSAGE_QUESTION; break;
default:
case /* JOptionPane.PLAIN_MESSAGE */ -1: gmessageType = GTK_MESSAGE_OTHER; break;
}
// convert Java strings to C strings
AutoReleaseStringUTF8 cprimaryText( env, primaryText );
AutoReleaseStringUTF8 csecondaryText( env, secondaryText );
// create GTK file chooser dialog
// https://docs.gtk.org/gtk3/class.MessageDialog.html
jint buttonCount = env->GetArrayLength( buttons );
GtkWidget* dialog = gtk_message_dialog_new( window, GTK_DIALOG_MODAL, gmessageType,
(buttonCount > 0) ? GTK_BUTTONS_NONE : GTK_BUTTONS_OK,
"%s", (const gchar*) cprimaryText );
if( csecondaryText != NULL )
gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( dialog ), "%s", (const gchar*) csecondaryText );
// add buttons
for( int i = 0; i < buttonCount; i++ ) {
AutoReleaseStringUTF8 str( env, (jstring) env->GetObjectArrayElement( buttons, i ) );
gtk_dialog_add_button( GTK_DIALOG( dialog ), str, i );
}
// set default button
gtk_dialog_set_default_response( GTK_DIALOG( dialog ), MIN( MAX( defaultButton, 0 ), buttonCount - 1 ) );
// show message dialog
gint responseID = gtk_dialog_run( GTK_DIALOG( dialog ) );
gtk_widget_destroy( dialog );
// return -1 if closed with ESC key
return (responseID >= 0) ? responseID : -1;
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2024 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.
*/
// avoid inlining of printf()
#define _NO_CRT_STDIO_INLINE
#include <dlfcn.h>
#include "JNIUtils.h"
/**
* @author Karl Tauber
*/
//---- class AutoReleaseStringUTF8 --------------------------------------------
AutoReleaseStringUTF8::AutoReleaseStringUTF8( JNIEnv* _env, jstring _javaString ) {
env = _env;
javaString = _javaString;
chars = (javaString != NULL) ? env->GetStringUTFChars( javaString, NULL ) : NULL;
}
AutoReleaseStringUTF8::~AutoReleaseStringUTF8() {
if( chars != NULL )
env->ReleaseStringUTFChars( javaString, chars );
}
//---- JNI methods ------------------------------------------------------------
extern "C"
JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_isLibAvailable
( JNIEnv* env, jclass cls, jstring libname )
{
AutoReleaseStringUTF8 clibname( env, libname );
void* lib = dlopen( clibname, RTLD_LAZY );
if( lib == NULL )
return false;
dlclose( lib );
return true;
}

View File

@@ -25,18 +25,21 @@
*/
bool sendEvent( JNIEnv *env, jobject window, const char *atom_name,
long data0, long data1, long data2, long data3, long data4 );
bool isWMHintSupported( Display* display, Window rootWindow, Atom atom );
// declare exported methods
Window getWindowHandle( JNIEnv* env, JAWT* awt, jobject window, Display** display_return );
// declare internal methods
static bool sendEvent( JNIEnv *env, jobject window, const char *atom_name,
long data0, long data1, long data2, long data3, long data4 );
static bool isWMHintSupported( Display* display, Window rootWindow, Atom atom );
//---- JNI methods ------------------------------------------------------------
/**
* 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"
@@ -79,7 +82,7 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_xS
0 );
}
bool sendEvent( JNIEnv *env, jobject window, const char *atom_name,
static bool sendEvent( JNIEnv *env, jobject window, const char *atom_name,
long data0, long data1, long data2, long data3, long data4 )
{
// get the AWT
@@ -131,7 +134,7 @@ bool sendEvent( JNIEnv *env, jobject window, const char *atom_name,
}
bool isWMHintSupported( Display* display, Window rootWindow, Atom atom ) {
static bool isWMHintSupported( Display* display, Window rootWindow, Atom atom ) {
Atom type;
int format;
unsigned long n_atoms;

View File

@@ -0,0 +1,36 @@
/*
* 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 <gtk/gtk.h>
#include <jni.h>
/**
* @author Karl Tauber
*/
//---- class AutoReleaseStringUTF8 --------------------------------------------
class AutoReleaseStringUTF8 {
JNIEnv* env;
jstring javaString;
const char* chars;
public:
AutoReleaseStringUTF8( JNIEnv* _env, jstring _javaString );
~AutoReleaseStringUTF8();
operator const gchar*() { return chars; }
};

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,30 @@ 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: isLibAvailable
* Signature: (Ljava/lang/String;)Z
*/
JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_isLibAvailable
(JNIEnv *, jclass, jstring);
/*
* 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;
*/
JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_showFileChooser
(JNIEnv *, jclass, jobject, jboolean, jstring, jstring, jstring, jstring, jint, jint, jobject, jint, jobjectArray);
/*
* Class: com_formdev_flatlaf_ui_FlatNativeLinuxLibrary
* Method: showMessageDialog
* Signature: (JILjava/lang/String;Ljava/lang/String;I[Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_showMessageDialog
(JNIEnv *, jclass, jlong, jint, jstring, jstring, jint, jobjectArray);
#ifdef __cplusplus
}
#endif

View File

@@ -75,7 +75,7 @@ tasks {
compilerArgs.addAll( toolChain.map {
when( it ) {
is Gcc, is Clang -> listOf( "-x", "objective-c++", "-mmacosx-version-min=$minOs" )
is Gcc, is Clang -> listOf( "-x", "objective-c++", "-mmacosx-version-min=$minOs", "-fvisibility=hidden" )
else -> emptyList()
}
} )

View File

@@ -46,7 +46,33 @@
JNI_COCOA_CATCH() \
}
#define JNI_THREAD_ENTER( jvm, returnValue ) \
JNIEnv *env; \
bool detach = false; \
switch( jvm->GetEnv( (void**) &env, JNI_VERSION_1_6 ) ) { \
case JNI_OK: break; \
case JNI_EDETACHED: \
if( jvm->AttachCurrentThread( (void**) &env, NULL ) != JNI_OK ) \
return returnValue; \
detach = true; \
break; \
default: return returnValue; \
} \
@try {
#define JNI_THREAD_EXIT( jvm ) \
} @finally { \
if( env->ExceptionCheck() ) \
env->ExceptionDescribe(); \
if( detach ) \
jvm->DetachCurrentThread(); \
}
jclass findClass( JNIEnv *env, const char* className, bool globalRef );
jfieldID getFieldID( JNIEnv *env, jclass cls, const char* fieldName, const char* fieldSignature, bool staticField );
jmethodID getMethodID( JNIEnv *env, jclass cls, const char* methodName, const char* methodSignature, bool staticMethod );
NSString* JavaToNSString( JNIEnv *env, jstring javaString );
jstring NSToJavaString( JNIEnv *env, NSString *nsString );
jstring NormalizedPathJavaFromNSString( JNIEnv* env, NSString *nsString );

View File

@@ -13,6 +13,32 @@ extern "C" {
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTONS_SPACING_MEDIUM 1L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTONS_SPACING_LARGE
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTONS_SPACING_LARGE 2L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canChooseFiles
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canChooseFiles 1L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canChooseDirectories
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canChooseDirectories 2L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_resolvesAliases
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_resolvesAliases 4L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_allowsMultipleSelection
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_allowsMultipleSelection 8L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_accessoryViewDisclosed
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_accessoryViewDisclosed 16L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_showsTagField
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_showsTagField 256L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canCreateDirectories
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canCreateDirectories 512L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canSelectHiddenExtension
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canSelectHiddenExtension 1024L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_showsHiddenFiles
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_showsHiddenFiles 2048L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_extensionHidden
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_extensionHidden 4096L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_allowsOtherFileTypes
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_allowsOtherFileTypes 8192L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_treatsFilePackagesAsDirectories
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_treatsFilePackagesAsDirectories 16384L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_showSingleFilterField
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_showSingleFilterField 16777216L
/*
* Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary
* Method: setWindowRoundedBorder
@@ -53,6 +79,22 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_isWi
JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_toggleWindowFullScreen
(JNIEnv *, jclass, jobject);
/*
* Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary
* Method: showFileChooser
* Signature: (Ljava/awt/Window;IZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILcom/formdev/flatlaf/ui/FlatNativeMacLibrary/FileChooserCallback;I[Ljava/lang/String;)[Ljava/lang/String;
*/
JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_showFileChooser
(JNIEnv *, jclass, jobject, jint, jboolean, jstring, jstring, jstring, jstring, jstring, jstring, jstring, jint, jint, jobject, jint, jobjectArray);
/*
* Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary
* Method: showMessageDialog
* Signature: (JILjava/lang/String;Ljava/lang/String;I[Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_showMessageDialog
(JNIEnv *, jclass, jlong, jint, jstring, jstring, jint, jobjectArray);
#ifdef __cplusplus
}
#endif

View File

@@ -14,8 +14,8 @@
* limitations under the License.
*/
#include <jni.h>
#include "com_formdev_flatlaf_ui_FlatNativeLibrary.h"
#import <jni.h>
#import "com_formdev_flatlaf_ui_FlatNativeLibrary.h"
/**
* @author Karl Tauber
@@ -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.FlatNativeMacLibrary
#define API_VERSION_MACOS 2001
#define API_VERSION_MACOS 2002
//---- JNI methods ------------------------------------------------------------

View File

@@ -75,3 +75,38 @@ jmethodID getMethodID( JNIEnv *env, jclass cls, const char* methodName, const ch
return methodID;
}
NSString* JavaToNSString( JNIEnv *env, jstring javaString ) {
if( javaString == NULL )
return NULL;
int len = env->GetStringLength( javaString );
const jchar* chars = env->GetStringChars( javaString, NULL );
if( chars == NULL )
return NULL;
NSString* nsString = [NSString stringWithCharacters:(unichar*)chars length:len];
env->ReleaseStringChars( javaString, chars );
return nsString;
}
jstring NSToJavaString( JNIEnv *env, NSString *nsString ) {
if( nsString == NULL )
return NULL;
jsize len = [nsString length];
unichar* buffer = (unichar*) calloc( len, sizeof( unichar ) );
if( buffer == NULL )
return NULL;
[nsString getCharacters:buffer];
jstring javaString = env->NewString( buffer, len );
free( buffer );
return javaString;
}
jstring NormalizedPathJavaFromNSString( JNIEnv* env, NSString *nsString ) {
return (nsString != NULL)
? NSToJavaString( env, [nsString precomposedStringWithCanonicalMapping] )
: NULL;
}

View File

@@ -0,0 +1,406 @@
/*
* Copyright 2024 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.
*/
#import <Cocoa/Cocoa.h>
#import <objc/runtime.h>
#import <jni.h>
#import "JNIUtils.h"
#import "JNFRunLoop.h"
#import "com_formdev_flatlaf_ui_FlatNativeMacLibrary.h"
/**
* @author Karl Tauber
* @since 3.7
*/
// declare internal methods
static jobjectArray newJavaStringArray( JNIEnv* env, jsize count );
static jobjectArray urlsToStringArray( JNIEnv* env, NSArray* urls );
static NSArray* getDialogURLs( NSSavePanel* dialog );
//---- class FileChooserDelegate ----------------------------------------------
@interface FileChooserDelegate : NSObject <NSOpenSavePanelDelegate, NSWindowDelegate> {
NSArray* _filters;
JavaVM* _jvm;
jobject _callback;
NSMutableSet* _urlsSet;
}
@property (nonatomic, assign) NSSavePanel* dialog;
- (void) initFilterAccessoryView: (NSMutableArray*)filters :(int)filterIndex
:(NSString*)filterFieldLabel :(bool)showSingleFilterField;
- (void) selectFormat: (id)sender;
- (void) selectFormatAtIndex: (int)index;
@end
@implementation FileChooserDelegate
- (void) initFilterAccessoryView: (NSMutableArray*)filters :(int)filterIndex
:(NSString*)filterFieldLabel :(bool)showSingleFilterField
{
_filters = filters;
// get filter names
NSArray* filterNames = filters.lastObject;
[filters removeLastObject];
// do not add filter/format combobox if there is only one filter
if( filters.count <= 1 && !showSingleFilterField ) {
[self selectFormatAtIndex:0];
return;
}
// create label
NSTextField* label = [[NSTextField alloc] initWithFrame:NSZeroRect];
label.stringValue = (filterFieldLabel != NULL) ? filterFieldLabel : @"Format:";
label.editable = NO;
label.bordered = NO;
label.bezeled = NO;
label.drawsBackground = NO;
// create combobox
NSPopUpButton* popupButton = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO];
[popupButton addItemsWithTitles:filterNames];
[popupButton selectItemAtIndex:MIN( MAX( filterIndex, 0 ), filterNames.count - 1 )];
[popupButton setTarget:self];
[popupButton setAction:@selector(selectFormat:)];
// create view
NSView* accessoryView = [[NSView alloc] initWithFrame:NSZeroRect];
[accessoryView addSubview:label];
[accessoryView addSubview:popupButton];
// autolayout
label.translatesAutoresizingMaskIntoConstraints = NO;
popupButton.translatesAutoresizingMaskIntoConstraints = NO;
int labelWidth = label.intrinsicContentSize.width;
int gap = 12;
int popupButtonWidth = popupButton.intrinsicContentSize.width;
int popupButtonMinimumWidth = 140;
int totalWidth = labelWidth + gap + MAX( popupButtonWidth, popupButtonMinimumWidth );
[accessoryView addConstraints:@[
// horizontal layout
[label.leadingAnchor constraintEqualToAnchor:accessoryView.centerXAnchor constant:-(totalWidth / 2)],
[popupButton.leadingAnchor constraintEqualToAnchor:label.trailingAnchor constant:gap],
[popupButton.widthAnchor constraintGreaterThanOrEqualToConstant:popupButtonMinimumWidth],
// vertical layout
[popupButton.topAnchor constraintEqualToAnchor:accessoryView.topAnchor constant:8],
[popupButton.bottomAnchor constraintEqualToAnchor:accessoryView.bottomAnchor constant:-8],
[label.firstBaselineAnchor constraintEqualToAnchor:popupButton.firstBaselineAnchor],
]];
[_dialog setAccessoryView:accessoryView];
// initial filter
[self selectFormatAtIndex:filterIndex];
}
- (void) selectFormat: (id)sender {
NSPopUpButton* popupButton = (NSPopUpButton*) sender;
[self selectFormatAtIndex:popupButton.indexOfSelectedItem];
}
- (void) selectFormatAtIndex: (int)index {
index = MIN( MAX( index, 0 ), _filters.count - 1 );
NSArray* fileTypes = [_filters objectAtIndex:index];
// use deprecated allowedFileTypes instead of newer allowedContentTypes (since macOS 11+)
// to support older macOS versions 10.14+ and because of some problems with allowedContentTypes:
// https://github.com/chromium/chromium/blob/d8e0032963b7ca4728ff4117933c0feb3e479b7a/components/remote_cocoa/app_shim/select_file_dialog_bridge.mm#L209-232
_dialog.allowedFileTypes = [fileTypes containsObject:@"*"] ? nil : fileTypes;
}
//---- NSOpenSavePanelDelegate ----
- (void) initCallback: (JavaVM*)jvm :(jobject)callback {
_jvm = jvm;
_callback = callback;
}
- (BOOL) panel: (id) sender validateURL:(NSURL*) url error:(NSError**) outError {
JNI_COCOA_TRY()
if( _callback == NULL )
return true;
NSArray* urls = getDialogURLs( sender );
// if multiple files are selected for opening, then the validateURL method
// is invoked for earch file, but our callback should be invoked only once for all files
if( urls != NULL && urls.count > 1 ) {
if( _urlsSet == NULL ) {
// invoked for first selected file --> invoke callback
_urlsSet = [NSMutableSet setWithArray:urls];
[_urlsSet removeObject:url];
} else {
// invoked for other selected files --> do not invoke callback
[_urlsSet removeObject:url];
if( _urlsSet.count == 0 )
_urlsSet = NULL;
return true;
}
}
JNI_THREAD_ENTER( _jvm, true )
jobjectArray files = urlsToStringArray( env, urls );
jlong window = (jlong) sender;
// invoke callback: boolean approve( String[] files, long hwnd );
jclass cls = env->GetObjectClass( _callback );
jmethodID approveID = env->GetMethodID( cls, "approve", "([Ljava/lang/String;J)Z" );
if( approveID != NULL && !env->CallBooleanMethod( _callback, approveID, files, window ) ) {
_urlsSet = NULL;
return false; // keep dialog open
}
JNI_THREAD_EXIT( _jvm )
JNI_COCOA_CATCH()
return true;
}
//---- NSWindowDelegate ----
- (void) windowDidBecomeMain:(NSNotification *) notification {
JNI_COCOA_TRY()
// Disable main menu bar because the file dialog is modal and it should be not possible
// to select any menu item. Otherwiese an action could show a Swing dialog, which would
// be shown under the file dialog.
//
// NOTE: It is not necessary to re-enable the main menu bar because Swing does this itself.
// When the file dialog is closed and a Swing window becomes active,
// macOS sends windowDidBecomeMain (and windowDidBecomeKey) message to AWTWindow,
// which invokes [self activateWindowMenuBar],
// which invokes [CMenuBar activate:menuBar modallyDisabled:isDisabled],
// which updates main menu bar.
NSMenu* mainMenu = [NSApp mainMenu];
int count = [mainMenu numberOfItems];
for( int i = 0; i < count; i++ ) {
NSMenuItem* menuItem = [mainMenu itemAtIndex:i];
NSMenu *subenu = [menuItem submenu];
if( [subenu isJavaMenu] )
[menuItem setEnabled:NO];
}
JNI_COCOA_CATCH()
}
@end
//---- helper -----------------------------------------------------------------
#define isOptionSet( option ) ((optionsSet & com_formdev_flatlaf_ui_FlatNativeMacLibrary_ ## option) != 0)
#define isOptionClear( option ) ((optionsClear & com_formdev_flatlaf_ui_FlatNativeMacLibrary_ ## option) != 0)
#define isOptionSetOrClear( option ) isOptionSet( option ) || isOptionClear( option )
static jobjectArray newJavaStringArray( JNIEnv* env, jsize count ) {
jclass stringClass = env->FindClass( "java/lang/String" );
return env->NewObjectArray( count, stringClass, NULL );
}
static NSMutableArray* initFilters( JNIEnv* env, jobjectArray fileTypes ) {
jint length = env->GetArrayLength( fileTypes );
if( length <= 0 )
return NULL;
NSMutableArray* filterNames = [NSMutableArray array];
NSMutableArray* filters = [NSMutableArray array];
NSString* filterName = NULL;
NSMutableArray* filter = NULL;
for( int i = 0; i < length; i++ ) {
jstring jstr = (jstring) env->GetObjectArrayElement( fileTypes, i );
if( jstr == NULL ) {
if( filter != NULL ) {
if( filter.count > 0 ) {
[filterNames addObject:filterName];
[filters addObject:filter];
}
filterName = NULL;
filter = NULL;
}
continue;
}
NSString* str = JavaToNSString( env, jstr );
env->DeleteLocalRef( jstr );
if( filter == NULL ) {
filterName = str;
filter = [NSMutableArray array];
} else
[filter addObject:str];
}
if( filters.count == 0 )
return NULL;
// add filter names to array (removed again after creating combobox)
[filters addObject:filterNames];
return filters;
}
//---- JNI methods ------------------------------------------------------------
extern "C"
JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_showFileChooser
( JNIEnv* env, jclass cls, jobject owner, jint dark, jboolean open,
jstring title, jstring prompt, jstring message, jstring filterFieldLabel,
jstring nameFieldLabel, jstring nameFieldStringValue, jstring directoryURL,
jint optionsSet, jint optionsClear, jobject callback, jint fileTypeIndex, jobjectArray fileTypes )
{
JNI_COCOA_ENTER()
JavaVM* jvm;
if( env->GetJavaVM( &jvm ) != JNI_OK )
return NULL;
// convert Java strings to NSString (on Java thread)
NSString* nsTitle = JavaToNSString( env, title );
NSString* nsPrompt = JavaToNSString( env, prompt );
NSString* nsMessage = JavaToNSString( env, message );
NSString* nsFilterFieldLabel = JavaToNSString( env, filterFieldLabel );
NSString* nsNameFieldLabel = JavaToNSString( env, nameFieldLabel );
NSString* nsNameFieldStringValue = JavaToNSString( env, nameFieldStringValue );
NSString* nsDirectoryURL = JavaToNSString( env, directoryURL );
NSMutableArray* filters = initFilters( env, fileTypes );
NSArray* urls = NULL;
NSArray** purls = &urls;
// show file dialog on macOS thread
[FlatJNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){
JNI_COCOA_TRY()
// create open/save panel
NSSavePanel* dialog = open ? [NSOpenPanel openPanel] : [NSSavePanel savePanel];
// set appearance
if( dark == 1 )
dialog.appearance = [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua];
else if( dark == 0 )
dialog.appearance = [NSAppearance appearanceNamed:NSAppearanceNameAqua];
if( nsTitle != NULL )
dialog.title = nsTitle;
if( nsPrompt != NULL )
dialog.prompt = nsPrompt;
if( nsMessage != NULL )
dialog.message = nsMessage;
if( nsNameFieldLabel != NULL )
dialog.nameFieldLabel = nsNameFieldLabel;
if( nsNameFieldStringValue != NULL )
dialog.nameFieldStringValue = nsNameFieldStringValue;
if( nsDirectoryURL != NULL )
dialog.directoryURL = [NSURL fileURLWithPath:nsDirectoryURL isDirectory:YES];
// set open options
if( open ) {
NSOpenPanel* openDialog = (NSOpenPanel*) dialog;
bool canChooseFiles = isOptionSet( FC_canChooseFiles );
bool canChooseDirectories = isOptionSet( FC_canChooseDirectories );
if( !canChooseFiles && !canChooseDirectories )
canChooseFiles = true;
openDialog.canChooseFiles = canChooseFiles;
openDialog.canChooseDirectories = canChooseDirectories;
if( isOptionSetOrClear( FC_resolvesAliases ) )
openDialog.resolvesAliases = isOptionSet( FC_resolvesAliases );
if( isOptionSetOrClear( FC_allowsMultipleSelection ) )
openDialog.allowsMultipleSelection = isOptionSet( FC_allowsMultipleSelection );
}
// set options
if( isOptionSetOrClear( FC_showsTagField ) )
dialog.showsTagField = isOptionSet( FC_showsTagField );
if( isOptionSetOrClear( FC_canCreateDirectories ) )
dialog.canCreateDirectories = isOptionSet( FC_canCreateDirectories );
if( isOptionSetOrClear( FC_canSelectHiddenExtension ) )
dialog.canSelectHiddenExtension = isOptionSet( FC_canSelectHiddenExtension );
if( isOptionSetOrClear( FC_showsHiddenFiles) )
dialog.showsHiddenFiles = isOptionSet( FC_showsHiddenFiles);
if( isOptionSetOrClear( FC_extensionHidden ) )
dialog.extensionHidden = isOptionSet( FC_extensionHidden );
if( isOptionSetOrClear( FC_allowsOtherFileTypes ) )
dialog.allowsOtherFileTypes = isOptionSet( FC_allowsOtherFileTypes );
if( isOptionSetOrClear( FC_treatsFilePackagesAsDirectories ) )
dialog.treatsFilePackagesAsDirectories = isOptionSet( FC_treatsFilePackagesAsDirectories );
FileChooserDelegate* delegate = [FileChooserDelegate new];
delegate.dialog = dialog;
// initialize filter accessory view
if( filters != NULL ) {
[delegate initFilterAccessoryView:filters :fileTypeIndex :nsFilterFieldLabel :isOptionSet( FC_showSingleFilterField )];
if( open && isOptionSetOrClear( FC_accessoryViewDisclosed ) )
((NSOpenPanel*)dialog).accessoryViewDisclosed = isOptionSet( FC_accessoryViewDisclosed );
}
// initialize callback
if( callback != NULL )
[delegate initCallback :jvm :callback];
// set file dialog delegate
dialog.delegate = delegate;
// show dialog
NSModalResponse response = [dialog runModal];
[delegate release];
if( response != NSModalResponseOK ) {
*purls = @[];
return;
}
*purls = getDialogURLs( dialog );
JNI_COCOA_CATCH()
}];
if( urls == NULL )
return NULL;
// convert URLs to Java string array
return urlsToStringArray( env, urls );
JNI_COCOA_EXIT()
}
static NSArray* getDialogURLs( NSSavePanel* dialog ) {
if( [dialog isKindOfClass:[NSOpenPanel class]] )
return [[NSArray alloc] initWithArray: static_cast<NSOpenPanel*>(dialog).URLs];
NSURL* url = dialog.URL;
// use '[[NSArray alloc] initWithObject:url]' here because '@[url]' crashes on macOS 10.14
return (url != NULL) ? [[NSArray alloc] initWithObject:url] : @[];
}
static jobjectArray urlsToStringArray( JNIEnv* env, NSArray* urls ) {
jsize count = (urls != NULL) ? urls.count : 0;
jobjectArray array = newJavaStringArray( env, count );
for( int i = 0; i < count; i++ ) {
jstring filename = NormalizedPathJavaFromNSString( env, [urls[i] path] );
env->SetObjectArrayElement( array, i, filename );
env->DeleteLocalRef( filename );
}
return array;
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2024 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.
*/
#import <Cocoa/Cocoa.h>
#import <objc/runtime.h>
#import <jni.h>
#import "JNIUtils.h"
#import "JNFRunLoop.h"
#import "com_formdev_flatlaf_ui_FlatNativeMacLibrary.h"
/**
* @author Karl Tauber
* @since 3.7
*/
extern "C"
JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_showMessageDialog
( JNIEnv* env, jclass cls, jlong hwndParent, jint alertStyle, jstring messageText, jstring informativeText,
jint defaultButton, jobjectArray buttons )
{
JNI_COCOA_ENTER()
// convert Java strings to NSString (on Java thread)
NSString* nsMessageText = JavaToNSString( env, messageText );
NSString* nsInformativeText = JavaToNSString( env, informativeText );
jint buttonCount = env->GetArrayLength( buttons );
NSMutableArray* nsButtons = [NSMutableArray array];
for( int i = 0; i < buttonCount; i++ ) {
NSString* nsButton = JavaToNSString( env, (jstring) env->GetObjectArrayElement( buttons, i ) );
[nsButtons addObject:nsButton];
}
jint result = -1;
jint* presult = &result;
// show alert on macOS thread
[FlatJNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){
NSAlert* alert = [[NSAlert alloc] init];
// use appearance from parent window
NSWindow* parent = (NSWindow*) hwndParent;
if( parent != NULL )
alert.window.appearance = parent.appearance;
// use empty string because if alert.messageText is not set it displays "Alert"
alert.messageText = (nsMessageText != NULL) ? nsMessageText : @"";
if( nsInformativeText != NULL )
alert.informativeText = nsInformativeText;
// alert style
switch( alertStyle ) {
case /* JOptionPane.ERROR_MESSAGE */ 0: alert.alertStyle = NSAlertStyleCritical; break;
default:
case /* JOptionPane.INFORMATION_MESSAGE */ 1: alert.alertStyle = NSAlertStyleInformational; break;
case /* JOptionPane.WARNING_MESSAGE */ 2: alert.alertStyle = NSAlertStyleWarning; break;
}
// add buttons
for( int i = 0; i < nsButtons.count; i++ ) {
NSButton* b = [alert addButtonWithTitle:nsButtons[i]];
if( i == defaultButton )
alert.window.defaultButtonCell = b.cell;
}
// show alert
NSInteger response = [alert runModal];
// if no buttons added, which shows a single OK button, the response is 0 when clicking OK
// if buttons added, response is 1000+buttonIndex
*presult = MAX( response - NSAlertFirstButtonReturn, 0 );
}];
return result;
JNI_COCOA_EXIT()
}

View File

@@ -39,13 +39,15 @@
@implementation WindowData
@end
// declare internal methods
// declare exported methods
NSWindow* getNSWindow( JNIEnv* env, jclass cls, jobject window );
WindowData* getWindowData( NSWindow* nsWindow, bool allocate );
void setWindowButtonsHidden( NSWindow* nsWindow, bool hidden );
int getWindowButtonAreaWidth( NSWindow* nsWindow );
int getWindowTitleBarHeight( NSWindow* nsWindow );
bool isWindowFullScreen( NSWindow* nsWindow );
// declare internal methods
static WindowData* getWindowData( NSWindow* nsWindow, bool allocate );
static void setWindowButtonsHidden( NSWindow* nsWindow, bool hidden );
static int getWindowButtonAreaWidth( NSWindow* nsWindow );
static int getWindowTitleBarHeight( NSWindow* nsWindow );
static bool isWindowFullScreen( NSWindow* nsWindow );
NSWindow* getNSWindow( JNIEnv* env, jclass cls, jobject window ) {
@@ -79,7 +81,7 @@ NSWindow* getNSWindow( JNIEnv* env, jclass cls, jobject window ) {
return (NSWindow *) jlong_to_ptr( env->GetLongField( platformWindow, ptrID ) );
}
WindowData* getWindowData( NSWindow* nsWindow, bool allocate ) {
static WindowData* getWindowData( NSWindow* nsWindow, bool allocate ) {
static char key;
WindowData* windowData = objc_getAssociatedObject( nsWindow, &key );
if( windowData == NULL && allocate ) {
@@ -252,7 +254,7 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setW
return FALSE;
}
void setWindowButtonsHidden( NSWindow* nsWindow, bool hidden ) {
static void setWindowButtonsHidden( NSWindow* nsWindow, bool hidden ) {
// get buttons
NSView* buttons[3] = {
[nsWindow standardWindowButton:NSWindowCloseButton],
@@ -312,7 +314,7 @@ JNIEXPORT jobject JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_getWi
return NULL;
}
int getWindowButtonAreaWidth( NSWindow* nsWindow ) {
static int getWindowButtonAreaWidth( NSWindow* nsWindow ) {
// get buttons
NSView* buttons[3] = {
[nsWindow standardWindowButton:NSWindowCloseButton],
@@ -344,7 +346,7 @@ int getWindowButtonAreaWidth( NSWindow* nsWindow ) {
return right + left;
}
int getWindowTitleBarHeight( NSWindow* nsWindow ) {
static int getWindowTitleBarHeight( NSWindow* nsWindow ) {
NSView* closeButton = [nsWindow standardWindowButton:NSWindowCloseButton];
if( closeButton == NULL )
return -1;
@@ -369,7 +371,7 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_isWi
return FALSE;
}
bool isWindowFullScreen( NSWindow* nsWindow ) {
static bool isWindowFullScreen( NSWindow* nsWindow ) {
return ((nsWindow.styleMask & NSWindowStyleMaskFullScreen) != 0);
}

View File

@@ -64,7 +64,7 @@ tasks {
compilerArgs.addAll( toolChain.map {
when( it ) {
is Gcc, is Clang -> listOf( "-O2", "-DUNICODE" )
is VisualCpp -> listOf( "/O2", "/Zl", "/GS-", "/DUNICODE" )
is VisualCpp -> listOf( "/O2", "/GS-", "/DUNICODE" )
else -> emptyList()
}
} )
@@ -80,8 +80,8 @@ tasks {
linkerArgs.addAll( toolChain.map {
when( it ) {
is Gcc, is Clang -> listOf( "-lUser32", "-lGdi32", "-lshell32", "-lAdvAPI32", "-lKernel32", "-lDwmapi" )
is VisualCpp -> listOf( "User32.lib", "Gdi32.lib", "shell32.lib", "AdvAPI32.lib", "Kernel32.lib", "Dwmapi.lib", "/NODEFAULTLIB" )
is Gcc, is Clang -> listOf( "-lUser32", "-lGdi32", "-lshell32", "-lAdvAPI32", "-lKernel32", "-lDwmapi", "-lOle32", "-luuid" )
is VisualCpp -> listOf( "User32.lib", "Gdi32.lib", "shell32.lib", "AdvAPI32.lib", "Kernel32.lib", "Dwmapi.lib", "Ole32.lib", "uuid.lib" )
else -> emptyList()
}
} )
@@ -93,6 +93,15 @@ tasks {
into( nativesDir )
rename( linkedFile.get().asFile.name, libraryName )
}
/*dump
val dumpbin = "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.42.34433/bin/Hostx64/x64/dumpbin.exe"
val dll = linkedFile.asFile.get()
val dllDir = dll.parent
exec { commandLine( dumpbin, "/all", "/rawdata:none", "/out:$dllDir/objdump.txt", dll ) }
exec { commandLine( dumpbin, "/all", "/out:$dllDir/full-contents.txt", dll ) }
exec { commandLine( dumpbin, "/disasm", "/out:$dllDir/disassemble.txt", dll ) }
dump*/
}
}
}

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.FlatNativeWindowsLibrary
#define API_VERSION_WINDOWS 1001
#define API_VERSION_WINDOWS 1002
//---- JNI methods ------------------------------------------------------------

View File

@@ -30,6 +30,7 @@
* @author Karl Tauber
*/
// declare exported methods
HWND getWindowHandle( JNIEnv* env, jobject window );
//---- JNI methods ------------------------------------------------------------

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2024 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.
*/
// avoid inlining of printf()
#define _NO_CRT_STDIO_INLINE
#include "JNIUtils.h"
/**
* @author Karl Tauber
*/
//---- class AutoReleaseString ------------------------------------------------
AutoReleaseString::AutoReleaseString( JNIEnv* _env, jstring _javaString ) {
env = _env;
javaString = _javaString;
chars = (javaString != NULL) ? env->GetStringChars( javaString, NULL ) : NULL;
}
AutoReleaseString::~AutoReleaseString() {
if( chars != NULL )
env->ReleaseStringChars( javaString, chars );
}
//---- class AutoReleaseStringArray -------------------------------------------
AutoReleaseStringArray::AutoReleaseStringArray( JNIEnv* _env, jobjectArray _javaStringArray ) {
env = _env;
count = (_javaStringArray != NULL) ? env->GetArrayLength( _javaStringArray ) : 0;
if( count <= 0 )
return;
javaStringArray = new jstring[count];
charsArray = new const jchar*[count];
for( int i = 0; i < count; i++ ) {
javaStringArray[i] = (jstring) env->GetObjectArrayElement( _javaStringArray, i );
charsArray[i] = env->GetStringChars( javaStringArray[i] , NULL );
}
}
AutoReleaseStringArray::~AutoReleaseStringArray() {
if( count == 0 )
return;
for( int i = 0; i < count; i++ ) {
env->ReleaseStringChars( javaStringArray[i], charsArray[i] );
env->DeleteLocalRef( javaStringArray[i] );
}
delete[] javaStringArray;
delete[] charsArray;
}

View File

@@ -36,8 +36,11 @@
* @author Karl Tauber
*/
HINSTANCE _instance;
extern "C"
BOOL WINAPI _DllMainCRTStartup( HINSTANCE instance, DWORD reason, LPVOID reserved ) {
_instance = instance;
return TRUE;
}

View File

@@ -0,0 +1,333 @@
/*
* Copyright 2024 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.
*/
// avoid inlining of printf()
#define _NO_CRT_STDIO_INLINE
#include <windows.h>
#include <shobjidl.h>
#include "JNIUtils.h"
#include "com_formdev_flatlaf_ui_FlatNativeWindowsLibrary.h"
/**
* @author Karl Tauber
* @since 3.7
*/
// declare external methods
extern HWND getWindowHandle( JNIEnv* env, jobject window );
// declare internal methods
static jobjectArray getFiles( JNIEnv* env, jboolean open, IFileDialog* dialog );
//---- class AutoReleasePtr ---------------------------------------------------
template<class T> class AutoReleasePtr {
T* ptr;
public:
AutoReleasePtr() {
ptr = NULL;
}
AutoReleasePtr( T* p ) {
ptr = p;
ptr->AddRef();
}
~AutoReleasePtr() {
if( ptr != NULL )
ptr->Release();
}
T** operator&() { return &ptr; }
T* operator->() { return ptr; }
operator T*() { return ptr; }
};
//---- class AutoReleaseIShellItem --------------------------------------------
class AutoReleaseIShellItem : public AutoReleasePtr<IShellItem> {
public:
AutoReleaseIShellItem( JNIEnv* env, jstring path ) {
AutoReleaseString cpath( env, path );
::SHCreateItemFromParsingName( cpath, NULL, IID_IShellItem, reinterpret_cast<void**>( &*this ) );
}
};
//---- class FilterSpec -------------------------------------------------------
class FilterSpec {
AutoReleaseStringArray fileTypes;
public:
UINT count = 0;
COMDLG_FILTERSPEC* specs = NULL;
public:
FilterSpec( JNIEnv* _env, jobjectArray _fileTypes )
: fileTypes( _env, _fileTypes )
{
if( fileTypes.count == 0 )
return;
count = fileTypes.count / 2;
specs = new COMDLG_FILTERSPEC[fileTypes.count];
for( int i = 0; i < count; i++ ) {
specs[i].pszName = fileTypes[i * 2];
specs[i].pszSpec = fileTypes[(i * 2) + 1];
}
}
~FilterSpec() {
if( specs != NULL )
delete[] specs;
}
};
//---- class DialogEventHandler -----------------------------------------------
// see https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/Win7Samples/winui/shell/appplatform/commonfiledialog/CommonFileDialogApp.cpp
class DialogEventHandler : public IFileDialogEvents {
JNIEnv* env;
jboolean open;
jobject callback;
LONG refCount = 1;
public:
DialogEventHandler( JNIEnv* _env, jboolean _open, jobject _callback ) {
env = _env;
open = _open;
callback = _callback;
}
//---- IFileDialogEvents methods ----
IFACEMETHODIMP OnFileOk( IFileDialog* dialog ) {
if( callback == NULL )
return S_OK;
// get files
jobjectArray files;
if( open ) {
AutoReleasePtr<IFileOpenDialog> openDialog;
HRESULT hr = dialog->QueryInterface( &openDialog );
files = SUCCEEDED( hr ) ? getFiles( env, true, openDialog ) : getFiles( env, false, dialog );
} else
files = getFiles( env, false, dialog );
// get hwnd of file dialog
HWND hwndFileDialog = 0;
AutoReleasePtr<IOleWindow> window;
if( SUCCEEDED( dialog->QueryInterface( &window ) ) )
window->GetWindow( &hwndFileDialog );
// invoke callback: boolean approve( String[] files, long hwnd );
jclass cls = env->GetObjectClass( callback );
jmethodID approveID = env->GetMethodID( cls, "approve", "([Ljava/lang/String;J)Z" );
if( approveID == NULL )
return S_OK;
return env->CallBooleanMethod( callback, approveID, files, hwndFileDialog ) ? S_OK : S_FALSE;
}
IFACEMETHODIMP OnFolderChange( IFileDialog* ) { return S_OK; }
IFACEMETHODIMP OnFolderChanging( IFileDialog*, IShellItem* ) { return S_OK; }
IFACEMETHODIMP OnHelp( IFileDialog* ) { return S_OK; }
IFACEMETHODIMP OnSelectionChange( IFileDialog* ) { return S_OK; }
IFACEMETHODIMP OnShareViolation( IFileDialog*, IShellItem*, FDE_SHAREVIOLATION_RESPONSE* ) { return S_OK; }
IFACEMETHODIMP OnTypeChange( IFileDialog*pfd ) { return S_OK; }
IFACEMETHODIMP OnOverwrite( IFileDialog*, IShellItem*, FDE_OVERWRITE_RESPONSE* ) { return S_OK; }
//---- IUnknown methods ----
IFACEMETHODIMP QueryInterface( REFIID riid, void** ppv ) {
if( riid != IID_IFileDialogEvents && riid != IID_IUnknown )
return E_NOINTERFACE;
*ppv = static_cast<IFileDialogEvents*>( this );
AddRef();
return S_OK;
}
IFACEMETHODIMP_(ULONG) AddRef() {
return InterlockedIncrement( &refCount );
}
IFACEMETHODIMP_(ULONG) Release() {
LONG newRefCount = InterlockedDecrement( &refCount );
if( newRefCount == 0 )
delete this;
return newRefCount;
}
private:
~DialogEventHandler() {}
};
//---- class CoInitializer ----------------------------------------------------
class CoInitializer {
public:
bool initialized;
CoInitializer() {
HRESULT result = ::CoInitializeEx( NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE );
initialized = SUCCEEDED( result );
}
~CoInitializer() {
if( initialized )
::CoUninitialize();
}
};
//---- helper -----------------------------------------------------------------
#define isOptionSet( option ) ((optionsSet & com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_ ## option) != 0)
#define CHECK_HRESULT( code ) { if( (code) != S_OK ) return NULL; }
static jobjectArray newJavaStringArray( JNIEnv* env, jsize count ) {
jclass stringClass = env->FindClass( "java/lang/String" );
return env->NewObjectArray( count, stringClass, NULL );
}
static jstring newJavaString( JNIEnv* env, LPWSTR str ) {
return env->NewString( reinterpret_cast<jchar*>( str ), static_cast<jsize>( wcslen( str ) ) );
}
//---- JNI methods ------------------------------------------------------------
extern "C"
JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_showFileChooser
( JNIEnv* env, jclass cls, jobject owner, jboolean open,
jstring title, jstring okButtonLabel, jstring fileNameLabel, jstring fileName,
jstring folder, jstring saveAsItem, jstring defaultFolder, jstring defaultExtension,
jint optionsSet, jint optionsClear, jobject callback, jint fileTypeIndex, jobjectArray fileTypes )
{
// initialize COM library
CoInitializer coInitializer;
if( !coInitializer.initialized )
return NULL;
// handle limitations (without this, some Win32 method fails and this method returns NULL)
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 );
AutoReleaseString cokButtonLabel( env, okButtonLabel );
AutoReleaseString cfileNameLabel( env, fileNameLabel );
AutoReleaseString cfileName( env, fileName );
AutoReleaseIShellItem cfolder( env, folder );
AutoReleaseIShellItem csaveAsItem( env, saveAsItem );
AutoReleaseIShellItem cdefaultFolder( env, defaultFolder );
AutoReleaseString cdefaultExtension( env, defaultExtension );
FilterSpec specs( env, fileTypes );
// create IFileOpenDialog or IFileSaveDialog
// https://learn.microsoft.com/en-us/windows/win32/shell/common-file-dialog
AutoReleasePtr<IFileDialog> dialog;
CHECK_HRESULT( ::CoCreateInstance( open ? CLSID_FileOpenDialog : CLSID_FileSaveDialog,
NULL, CLSCTX_INPROC_SERVER, open ? IID_IFileOpenDialog : IID_IFileSaveDialog,
reinterpret_cast<LPVOID*>( &dialog ) ) );
// set title, etc.
if( ctitle != NULL )
CHECK_HRESULT( dialog->SetTitle( ctitle ) );
if( cokButtonLabel != NULL )
CHECK_HRESULT( dialog->SetOkButtonLabel( cokButtonLabel ) );
if( cfileNameLabel != NULL )
CHECK_HRESULT( dialog->SetFileNameLabel( cfileNameLabel ) );
if( cfileName != NULL )
CHECK_HRESULT( dialog->SetFileName( cfileName ) );
if( cfolder != NULL )
CHECK_HRESULT( dialog->SetFolder( cfolder ) );
if( !open && csaveAsItem != NULL )
CHECK_HRESULT( ((IFileSaveDialog*)(IFileDialog*)dialog)->SetSaveAsItem( csaveAsItem ) );
if( cdefaultFolder != NULL )
CHECK_HRESULT( dialog->SetDefaultFolder( cdefaultFolder ) );
if( cdefaultExtension != NULL )
CHECK_HRESULT( dialog->SetDefaultExtension( cdefaultExtension ) );
// set options
FILEOPENDIALOGOPTIONS existingOptions;
CHECK_HRESULT( dialog->GetOptions( &existingOptions ) );
CHECK_HRESULT( dialog->SetOptions ( (existingOptions & ~optionsClear) | optionsSet ) );
// initialize filter
if( specs.count > 0 ) {
CHECK_HRESULT( dialog->SetFileTypes( specs.count, specs.specs ) );
if( fileTypeIndex > 0 )
CHECK_HRESULT( dialog->SetFileTypeIndex( min( fileTypeIndex + 1, specs.count ) ) );
}
// add event handler
AutoReleasePtr<DialogEventHandler> handler( new DialogEventHandler( env, open, callback ) );
DWORD dwCookie = 0;
CHECK_HRESULT( dialog->Advise( handler, &dwCookie ) );
// show dialog
HWND hwndOwner = (owner != NULL) ? getWindowHandle( env, owner ) : NULL;
HRESULT hr = dialog->Show( hwndOwner );
dialog->Unadvise( dwCookie );
if( hr == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
return newJavaStringArray( env, 0 );
CHECK_HRESULT( hr );
// get selected files as Java string array
return getFiles( env, open, dialog );
}
static jobjectArray getFiles( JNIEnv* env, jboolean open, IFileDialog* dialog ) {
if( open ) {
AutoReleasePtr<IShellItemArray> shellItems;
DWORD count;
CHECK_HRESULT( ((IFileOpenDialog*)(IFileDialog*)dialog)->GetResults( &shellItems ) );
CHECK_HRESULT( shellItems->GetCount( &count ) );
// convert shell items to Java string array
jobjectArray array = newJavaStringArray( env, count );
for( int i = 0; i < count; i++ ) {
AutoReleasePtr<IShellItem> shellItem;
LPWSTR path;
CHECK_HRESULT( shellItems->GetItemAt( i, &shellItem ) );
CHECK_HRESULT( shellItem->GetDisplayName( SIGDN_FILESYSPATH, &path ) );
jstring jpath = newJavaString( env, path );
CoTaskMemFree( path );
env->SetObjectArrayElement( array, i, jpath );
env->DeleteLocalRef( jpath );
}
return array;
} else {
AutoReleasePtr<IShellItem> shellItem;
LPWSTR path;
CHECK_HRESULT( dialog->GetResult( &shellItem ) );
CHECK_HRESULT( shellItem->GetDisplayName( SIGDN_FILESYSPATH, &path ) );
// convert shell item to Java string array
jstring jpath = newJavaString( env, path );
CoTaskMemFree( path );
jobjectArray array = newJavaStringArray( env, 1 );
env->SetObjectArrayElement( array, 0, jpath );
env->DeleteLocalRef( jpath );
return array;
}
}

View File

@@ -0,0 +1,419 @@
/*
* 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.
*/
// avoid inlining of printf()
#define _NO_CRT_STDIO_INLINE
#include <windows.h>
#include <stdio.h>
#include "JNIUtils.h"
#include "com_formdev_flatlaf_ui_FlatNativeWindowsLibrary.h"
/**
* @author Karl Tauber
* @since 3.7
*/
#define ID_BUTTON1 101
// declare external fields
extern HINSTANCE _instance;
// declare internal methods
static byte* createInMemoryTemplate( HWND owner, int messageType, LPCWSTR title, LPCWSTR text,
int defaultButton, int buttonCount, LPCWSTR* buttons );
static INT_PTR CALLBACK messageDialogProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
static int textLengthAsDLUs( HDC hdc, LPCWSTR str, int strLen );
static LONG pixel2dluX( LONG px );
static LONG pixel2dluY( LONG px );
static LONG dluX2pixel( LONG dluX );
static LPWORD lpwAlign( LPWORD lpIn );
extern "C"
JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_showMessageBox
( JNIEnv* env, jclass cls, jlong hwndParent, jstring text, jstring caption, jint type )
{
// convert Java strings to C strings
AutoReleaseString ctext( env, text );
AutoReleaseString ccaption( env, caption );
return ::MessageBox( reinterpret_cast<HWND>( hwndParent ), ctext, ccaption, type );
}
extern "C"
JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_showMessageDialog
( JNIEnv* env, jclass cls, jlong hwndParent, jint messageType, jstring title,
jstring text, jint defaultButton, jobjectArray buttons )
{
HWND owner = reinterpret_cast<HWND>( hwndParent );
// convert Java strings to C strings
AutoReleaseString ctitle( env, title );
AutoReleaseString ctext( env, text );
AutoReleaseStringArray cbuttons( env, buttons );
// get title from parent window if necessary
WCHAR parentTitle[100];
if( ctitle == NULL )
::GetWindowText( owner, parentTitle, 100 );
byte* templ = createInMemoryTemplate( owner, messageType, (ctitle != NULL) ? ctitle : parentTitle,
ctext, defaultButton, cbuttons.count, cbuttons );
if( templ == NULL )
return -1;
LRESULT ret = ::DialogBoxIndirect( _instance, (LPDLGTEMPLATE) templ, owner, messageDialogProc );
delete templ;
return (ret >= ID_BUTTON1) ? ret - ID_BUTTON1 : -1;
}
// all values in DLUs
#define INSETS_TOP 12
#define INSETS_LEFT 12
#define INSETS_RIGHT 12
#define INSETS_BOTTOM 6
#define ICON_TEXT_GAP 8
#define LABEL_MIN_WIDTH 100
#define LABEL_MAX_WIDTH 250
#define LABEL_HEIGHT 8
#define BUTTON_WIDTH 50
#define BUTTON_HEIGHT 12
#define BUTTON_GAP 5
#define BUTTON_TOP_GAP 14
#define BUTTON_LEFT_RIGHT_GAP 8
// based on https://learn.microsoft.com/en-us/windows/win32/dlgbox/using-dialog-boxes#creating-a-template-in-memory
static byte* createInMemoryTemplate( HWND owner, int messageType, LPCWSTR title, LPCWSTR text,
int defaultButton, int buttonCount, LPCWSTR* buttons )
{
// get font info needed for DS_SETFONT
NONCLIENTMETRICS ncMetrics;
ncMetrics.cbSize = sizeof( NONCLIENTMETRICS );
if( !::SystemParametersInfo( SPI_GETNONCLIENTMETRICS, 0, &ncMetrics, 0 ) )
return NULL;
// create DC to use message font
HDC hdcOwner = ::GetDC( owner );
HDC hdc = ::CreateCompatibleDC( hdcOwner );
::ReleaseDC( owner, hdcOwner );
if( hdc == NULL )
return NULL;
HFONT hfont = ::CreateFontIndirect( &ncMetrics.lfMessageFont );
if( hfont == NULL ) {
::DeleteDC( hdc );
return NULL;
}
if( ::SelectObject( hdc, hfont ) == NULL ) {
::DeleteDC( hdc );
::DeleteObject( hfont );
return NULL;
}
//---- calculate layout (in DLUs) ----
// layout icon
LPWSTR icon;
switch( messageType ) {
case /* JOptionPane.ERROR_MESSAGE */ 0: icon = IDI_ERROR; break;
case /* JOptionPane.INFORMATION_MESSAGE */ 1: icon = IDI_INFORMATION; break;
case /* JOptionPane.WARNING_MESSAGE */ 2: icon = IDI_WARNING; break;
case /* JOptionPane.QUESTION_MESSAGE */ 3: icon = IDI_QUESTION; break;
default:
case /* JOptionPane.PLAIN_MESSAGE */ -1: icon = NULL; break;
}
int ix = INSETS_LEFT;
int iy = INSETS_TOP;
int iw = pixel2dluX( ::GetSystemMetrics( SM_CXICON ) );
int ih = pixel2dluY( ::GetSystemMetrics( SM_CYICON ) );
// layout text
int tx = ix + (icon != NULL ? iw + ICON_TEXT_GAP : 0);
int ty = iy;
int tw = 0;
int th = 0;
if( text == NULL )
text = L"";
LPWSTR wrappedText = new WCHAR[wcslen( text ) + 1];
wcscpy( wrappedText, text );
LPWSTR lineStart = wrappedText;
for( LPWSTR t = wrappedText; ; t++ ) {
if( *t != '\n' && *t != 0 )
continue;
// calculate line width (in pixels) and number of charaters that fit into LABEL_MAX_WIDTH
int lineLen = t - lineStart;
int fit = 0;
SIZE size{ 0 };
if( !::GetTextExtentExPoint( hdc, lineStart, lineLen, dluX2pixel( LABEL_MAX_WIDTH ), &fit, NULL, &size ) )
break;
if( fit < lineLen ) {
// wrap too long line --> try to wrap at space character
bool wrapped = false;
for( LPWSTR t2 = lineStart + fit - 1; t2 > lineStart; t2-- ) {
if( *t2 == ' ' || *t2 == '\t' ) {
*t2 = '\n';
int w = textLengthAsDLUs( hdc, lineStart, t2 - lineStart );
tw = max( tw, w );
th += LABEL_HEIGHT;
// continue wrapping after inserted line break
t = t2;
lineStart = t + 1;
wrapped = true;
break;
}
}
if( !wrapped ) {
// not able to wrap at word --> break long word
int breakIndex = (lineStart + fit) - wrappedText;
int w = textLengthAsDLUs( hdc, lineStart, breakIndex );
tw = max( tw, w );
th += LABEL_HEIGHT;
// duplicate string
LPWSTR wrappedText2 = new WCHAR[wcslen( wrappedText ) + 1 + 1];
// use wcscpy(), instead of wcsncpy(), because this method is inlined and does not require linking to runtime lib
wcscpy( wrappedText2, wrappedText );
wrappedText2[breakIndex] = '\n';
wcscpy( wrappedText2 + breakIndex + 1, wrappedText + breakIndex );
// delete old text
delete[] wrappedText;
wrappedText = wrappedText2;
// continue wrapping after inserted line break
t = wrappedText + breakIndex;
lineStart = t + 1;
}
} else {
// line fits into LABEL_MAX_WIDTH
int w = pixel2dluX( size.cx );
tw = max( tw, w );
th += LABEL_HEIGHT;
lineStart = t + 1;
}
if( *t == 0 )
break;
}
tw = min( max( tw, LABEL_MIN_WIDTH ), LABEL_MAX_WIDTH );
th = max( th, LABEL_HEIGHT );
if( icon != NULL && th < ih )
ty += (ih - th) / 2; // vertically center text
// layout buttons
int* bw = new int[buttonCount];
int buttonTotalWidth = BUTTON_GAP * (buttonCount - 1);
for( int i = 0; i < buttonCount; i++ ) {
int w = textLengthAsDLUs( hdc, buttons[i], -1 ) + 16;
bw[i] = max( BUTTON_WIDTH, w );
buttonTotalWidth += bw[i];
}
// layout dialog
int dx = 0;
int dy = 0;
int dw = max( tx + tw + INSETS_RIGHT, BUTTON_LEFT_RIGHT_GAP + buttonTotalWidth + BUTTON_LEFT_RIGHT_GAP );
int dh = max( iy + ih, ty + th ) + BUTTON_TOP_GAP + BUTTON_HEIGHT + INSETS_BOTTOM;
// center dialog in owner
RECT ownerRect{ 0 };
if( ::GetClientRect( owner, &ownerRect ) ) {
dx = (pixel2dluX( ownerRect.right - ownerRect.left ) - dw) / 2;
dy = (pixel2dluY( ownerRect.bottom - ownerRect.top ) - dh) / 2;
}
// layout button area
int bx = dw - buttonTotalWidth - BUTTON_LEFT_RIGHT_GAP;
int by = dh - BUTTON_HEIGHT - INSETS_BOTTOM;
// get font info needed for DS_SETFONT
int fontPointSize = (ncMetrics.lfMessageFont.lfHeight < 0)
? -MulDiv( ncMetrics.lfMessageFont.lfHeight, 72, ::GetDeviceCaps( hdc, LOGPIXELSY ) )
: ncMetrics.lfMessageFont.lfHeight;
LPCWSTR fontFaceName = ncMetrics.lfMessageFont.lfFaceName;
// delete DC and font
::DeleteDC( hdc );
::DeleteObject( hfont );
// (approximately) calculate memory size needed for in-memory template
int templSize = (sizeof(DLGTEMPLATE) + /*menu*/ 2 + /*class*/ 2 + /*title*/ 2)
+ ((sizeof(DLGITEMTEMPLATE) + /*class*/ 4 + /*title/icon*/ 4 + /*creation data*/ 2) * (/*icon+text*/2 + buttonCount))
+ (title != NULL ? (wcslen( title ) + 1) * sizeof(wchar_t) : 0)
+ /*fontPointSize*/ 2 + ((wcslen( fontFaceName ) + 1) * sizeof(wchar_t))
+ ((wcslen( wrappedText ) + 1) * sizeof(wchar_t));
for( int i = 0; i < buttonCount; i++ )
templSize += ((wcslen( buttons[i] ) + 1) * sizeof(wchar_t));
templSize += (2 * (1 + 1 + buttonCount)); // necessary for DWORD alignment
templSize += 100; // some reserve
// allocate memory for in-memory template
byte* templ = new byte[templSize];
if( templ == NULL )
return NULL;
//---- define dialog box ----
LPDLGTEMPLATE lpdt = (LPDLGTEMPLATE) templ;
lpdt->style = WS_POPUP | WS_BORDER | WS_SYSMENU | DS_MODALFRAME | WS_CAPTION | DS_SETFONT;
lpdt->cdit = /*text*/ 1 + buttonCount; // number of controls
lpdt->x = dx;
lpdt->y = dy;
lpdt->cx = dw;
lpdt->cy = dh;
LPWORD lpw = (LPWORD) (lpdt + 1);
*lpw++ = 0; // no menu
*lpw++ = 0; // predefined dialog box class (by default)
if( title != NULL ) {
wcscpy( (LPWSTR) lpw, title );
lpw += wcslen( title ) + 1;
} else
*lpw++ = 0; // no title
// for DS_SETFONT
*lpw++ = fontPointSize;
wcscpy( (LPWSTR) lpw, fontFaceName );
lpw += wcslen( fontFaceName ) + 1;
//---- define icon ----
if( icon != NULL ) {
lpdt->cdit++;
lpw = lpwAlign( lpw );
LPDLGITEMTEMPLATE lpdit = (LPDLGITEMTEMPLATE) lpw;
lpdit->x = ix;
lpdit->y = iy;
lpdit->cx = iw;
lpdit->cy = ih;
lpdit->id = ID_BUTTON1 - 1;
lpdit->style = WS_CHILD | WS_VISIBLE | SS_ICON;
lpw = (LPWORD) (lpdit + 1);
*lpw++ = 0xffff; *lpw++ = 0x0082; // Static class
*lpw++ = 0xffff; *lpw++ = (WORD) icon; // icon
*lpw++ = 0; // creation data
}
//---- define text ----
lpw = lpwAlign( lpw );
LPDLGITEMTEMPLATE lpdit = (LPDLGITEMTEMPLATE) lpw;
lpdit->x = tx;
lpdit->y = ty;
lpdit->cx = tw;
lpdit->cy = th;
lpdit->id = ID_BUTTON1 - 2;
lpdit->style = WS_CHILD | WS_VISIBLE | SS_LEFT | SS_NOPREFIX | SS_EDITCONTROL;
lpw = (LPWORD) (lpdit + 1);
*lpw++ = 0xffff; *lpw++ = 0x0082; // Static class
wcscpy( (LPWSTR) lpw, wrappedText ); lpw += wcslen( wrappedText ) + 1; // text
*lpw++ = 0; // creation data
//---- define buttons ----
defaultButton = min( max( defaultButton, 0 ), buttonCount - 1 );
int buttonId = ID_BUTTON1;
for( int i = 0; i < buttonCount; i++ ) {
lpw = lpwAlign( lpw );
LPDLGITEMTEMPLATE lpdit = (LPDLGITEMTEMPLATE) lpw;
lpdit->x = bx;
lpdit->y = by;
lpdit->cx = bw[i];
lpdit->cy = BUTTON_HEIGHT;
lpdit->id = buttonId++;
lpdit->style = WS_CHILD | WS_VISIBLE | WS_TABSTOP | (i == 0 ? WS_GROUP : 0)
| BS_TEXT | (i == defaultButton ? BS_DEFPUSHBUTTON : BS_PUSHBUTTON);
lpw = (LPWORD) (lpdit + 1);
*lpw++ = 0xffff; *lpw++ = 0x0080; // Button class
wcscpy( (LPWSTR) lpw, buttons[i] ); lpw += wcslen( buttons[i] ) + 1; // text
*lpw++ = 0; // creation data
bx += bw[i] + BUTTON_GAP;
}
delete[] wrappedText;
delete[] bw;
return templ;
}
static BOOL CALLBACK focusDefaultButtonProc( HWND hwnd, LPARAM lParam ) {
if( ::GetWindowLong( hwnd, GWL_ID ) >= ID_BUTTON1 ) {
LONG style = ::GetWindowLong( hwnd, GWL_STYLE );
if( (style & BS_DEFPUSHBUTTON) != 0 ) {
::SetFocus( hwnd );
return FALSE;
}
}
return TRUE;
}
static INT_PTR CALLBACK messageDialogProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) {
switch( uMsg ) {
case WM_INITDIALOG:
::EnumChildWindows( hwnd, focusDefaultButtonProc, 0 );
break;
case WM_COMMAND:
::EndDialog( hwnd, wParam );
return TRUE;
}
return FALSE;
}
static int textLengthAsDLUs( HDC hdc, LPCWSTR str, int strLen ) {
SIZE size{ 0 };
::GetTextExtentPoint32( hdc, str, (strLen >= 0) ? strLen : wcslen( str ), &size );
return pixel2dluX( size.cx );
}
static LONG pixel2dluX( LONG px ) {
return MulDiv( px, 4, LOWORD( ::GetDialogBaseUnits() ) );
}
static LONG pixel2dluY( LONG py ) {
return MulDiv( py, 8, HIWORD( ::GetDialogBaseUnits() ) );
}
static LONG dluX2pixel( LONG dluX ) {
return MulDiv( dluX, LOWORD( ::GetDialogBaseUnits() ), 4 );
}
static LPWORD lpwAlign( LPWORD lpIn ) {
ULONG_PTR ul = (ULONG_PTR) lpIn;
ul += 3;
ul >>= 2;
ul <<= 2;
return (LPWORD) ul;
}

View File

@@ -25,8 +25,8 @@
* @author Karl Tauber
*/
// see FlatWndProc.cpp
HWND getWindowHandle( JNIEnv* env, jobject window );
// declare external methods
extern HWND getWindowHandle( JNIEnv* env, jobject window );
//---- Utility ----------------------------------------------------------------

View File

@@ -0,0 +1,53 @@
/*
* 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 <windows.h>
#include <jni.h>
/**
* @author Karl Tauber
*/
//---- class AutoReleaseString ------------------------------------------------
class AutoReleaseString {
JNIEnv* env;
jstring javaString;
const jchar* chars;
public:
AutoReleaseString( JNIEnv* _env, jstring _javaString );
~AutoReleaseString();
operator LPCWSTR() { return (LPCWSTR) chars; }
};
//---- class AutoReleaseStringArray -------------------------------------------
class AutoReleaseStringArray {
JNIEnv* env;
jstring* javaStringArray;
const jchar** charsArray;
public:
UINT count;
public:
AutoReleaseStringArray( JNIEnv* _env, jobjectArray _javaStringArray );
~AutoReleaseStringArray();
operator LPCWSTR*() { return (LPCWSTR*) charsArray; }
};

View File

@@ -27,6 +27,52 @@ extern "C" {
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_DWMWA_COLOR_DEFAULT -1L
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_DWMWA_COLOR_NONE
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_DWMWA_COLOR_NONE -2L
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_OVERWRITEPROMPT
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_OVERWRITEPROMPT 2L
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_STRICTFILETYPES
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_STRICTFILETYPES 4L
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NOCHANGEDIR
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NOCHANGEDIR 8L
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_PICKFOLDERS
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_PICKFOLDERS 32L
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_FORCEFILESYSTEM
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_FORCEFILESYSTEM 64L
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_ALLNONSTORAGEITEMS
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_ALLNONSTORAGEITEMS 128L
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NOVALIDATE
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NOVALIDATE 256L
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_ALLOWMULTISELECT
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_ALLOWMULTISELECT 512L
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_PATHMUSTEXIST
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_PATHMUSTEXIST 2048L
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_FILEMUSTEXIST
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_FILEMUSTEXIST 4096L
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_CREATEPROMPT
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_CREATEPROMPT 8192L
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_SHAREAWARE
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_SHAREAWARE 16384L
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NOREADONLYRETURN
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NOREADONLYRETURN 32768L
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NOTESTFILECREATE
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NOTESTFILECREATE 65536L
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_HIDEMRUPLACES
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_HIDEMRUPLACES 131072L
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_HIDEPINNEDPLACES
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_HIDEPINNEDPLACES 262144L
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NODEREFERENCELINKS
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NODEREFERENCELINKS 1048576L
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_OKBUTTONNEEDSINTERACTION
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_OKBUTTONNEEDSINTERACTION 2097152L
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_DONTADDTORECENT
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_DONTADDTORECENT 33554432L
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_FORCESHOWHIDDEN
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_FORCESHOWHIDDEN 268435456L
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_DEFAULTNOMINIMODE
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_DEFAULTNOMINIMODE 536870912L
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_FORCEPREVIEWPANEON
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_FORCEPREVIEWPANEON 1073741824L
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_SUPPORTSTREAMABLEITEMS
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_SUPPORTSTREAMABLEITEMS -2147483648L
/*
* Class: com_formdev_flatlaf_ui_FlatNativeWindowsLibrary
* Method: getOSBuildNumberImpl
@@ -67,6 +113,30 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_
JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_dwmSetWindowAttributeDWORD
(JNIEnv *, jclass, jlong, jint, jint);
/*
* Class: com_formdev_flatlaf_ui_FlatNativeWindowsLibrary
* Method: showFileChooser
* Signature: (Ljava/awt/Window;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILcom/formdev/flatlaf/ui/FlatNativeWindowsLibrary/FileChooserCallback;I[Ljava/lang/String;)[Ljava/lang/String;
*/
JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_showFileChooser
(JNIEnv *, jclass, jobject, jboolean, jstring, jstring, jstring, jstring, jstring, jstring, jstring, jstring, jint, jint, jobject, jint, jobjectArray);
/*
* Class: com_formdev_flatlaf_ui_FlatNativeWindowsLibrary
* Method: showMessageDialog
* Signature: (JILjava/lang/String;Ljava/lang/String;I[Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_showMessageDialog
(JNIEnv *, jclass, jlong, jint, jstring, jstring, jint, jobjectArray);
/*
* Class: com_formdev_flatlaf_ui_FlatNativeWindowsLibrary
* Method: showMessageBox
* Signature: (JLjava/lang/String;Ljava/lang/String;I)I
*/
JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_showMessageBox
(JNIEnv *, jclass, jlong, jstring, jstring, jint);
#ifdef __cplusplus
}
#endif