From d49282dfe8207996a2b4c7c1b0df45df0a53fdad Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sat, 11 Jan 2025 17:50:46 +0100 Subject: [PATCH] System File Chooser: support "approve" callback and system message dialog on Windows and Linux (not yet used in `SystemFileChooser` --- .../flatlaf/ui/FlatNativeLinuxLibrary.java | 35 +++++- .../flatlaf/ui/FlatNativeWindowsLibrary.java | 27 +++- .../flatlaf/util/SystemFileChooser.java | 6 +- .../src/main/cpp/GtkFileChooser.cpp | 95 ++++++++++++++- ...ormdev_flatlaf_ui_FlatNativeLinuxLibrary.h | 12 +- .../flatlaf-natives-windows/build.gradle.kts | 4 +- .../src/main/cpp/WinFileChooser.cpp | 115 +++++++++++++++++- ...mdev_flatlaf_ui_FlatNativeWindowsLibrary.h | 12 +- .../FlatSystemFileChooserLinuxTest.java | 20 ++- .../FlatSystemFileChooserLinuxTest.jfd | 8 +- .../FlatSystemFileChooserWindowsTest.java | 20 ++- .../FlatSystemFileChooserWindowsTest.jfd | 6 + 12 files changed, 335 insertions(+), 25 deletions(-) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeLinuxLibrary.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeLinuxLibrary.java index f1bed502..5ea7d178 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeLinuxLibrary.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeLinuxLibrary.java @@ -24,6 +24,7 @@ import java.awt.event.MouseEvent; import java.awt.geom.AffineTransform; import javax.swing.JDialog; import javax.swing.JFrame; +import javax.swing.JOptionPane; import com.formdev.flatlaf.util.SystemInfo; /** @@ -131,7 +132,7 @@ public class FlatNativeLinuxLibrary FC_create_folders = 1 << 5; // default for Save /** - * Shows the Linux system file dialog + * Shows the Linux/GTK system file dialog * GtkFileChooserDialog. *

* Uses {@code GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER} if {@link #FC_select_folder} is set in parameter {@code optionsSet}. @@ -151,6 +152,7 @@ public class FlatNativeLinuxLibrary * @param currentFolder current directory shown in the dialog; or {@code null} * @param optionsSet options to set; see {@code FOS_*} constants * @param optionsClear options to clear; see {@code FOS_*} constants + * @param callback approve callback; or {@code null} * @param fileTypeIndex the file type that appears as selected (zero-based) * @param fileTypes file types that the dialog can open or save. * Two or more strings and {@code null} are required for each filter. @@ -164,5 +166,34 @@ public class FlatNativeLinuxLibrary */ public native static String[] showFileChooser( Window owner, boolean open, String title, String okButtonLabel, String currentName, String currentFolder, - int optionsSet, int optionsClear, int fileTypeIndex, String... fileTypes ); + int optionsSet, int optionsClear, FileChooserCallback callback, + int fileTypeIndex, String... fileTypes ); + + /** @since 3.6 */ + public interface FileChooserCallback { + boolean approve( String[] files, long hwndFileDialog ); + } + + /** + * Shows a GTK message box + * GtkMessageDialog. + *

+ * For use in {@link FileChooserCallback} only. + * + * @param hwndParent the parent of the message box + * @param messageType type of message being displayed: + * {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#INFORMATION_MESSAGE}, + * {@link JOptionPane#WARNING_MESSAGE}, {@link JOptionPane#QUESTION_MESSAGE} or + * {@link JOptionPane#PLAIN_MESSAGE} + * @param primaryText primary text; if the dialog has a secondary text, + * this will appear as title in a larger bold font + * @param secondaryText secondary text; shown below of primary text; or {@code null} + * @param defaultButton index of the default button, which can be pressed using ENTER key + * @param buttons texts of the buttons; if no buttons given the a default "OK" button is shown + * @return index of pressed button; or -1 for ESC key + * + * @since 3.6 + */ + public native static int showMessageDialog( long hwndParent, int messageType, + String primaryText, String secondaryText, int defaultButton, String... buttons ); } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowsLibrary.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowsLibrary.java index 8dfa52a4..ec2bed8f 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowsLibrary.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowsLibrary.java @@ -218,6 +218,7 @@ public class FlatNativeWindowsLibrary * @param defaultExtension default extension to be added to file name in save dialog; or {@code null} * @param optionsSet options to set; see {@code FOS_*} constants * @param optionsClear options to clear; see {@code FOS_*} constants + * @param callback approve callback; or {@code null} * @param fileTypeIndex the file type that appears as selected (zero-based) * @param fileTypes file types that the dialog can open or save. * Pairs of strings are required for each filter. @@ -231,5 +232,29 @@ public class FlatNativeWindowsLibrary public native static String[] showFileChooser( Window owner, boolean open, String title, String okButtonLabel, String fileNameLabel, String fileName, String folder, String saveAsItem, String defaultFolder, String defaultExtension, - int optionsSet, int optionsClear, int fileTypeIndex, String... fileTypes ); + int optionsSet, int optionsClear, FileChooserCallback callback, + int fileTypeIndex, String... fileTypes ); + + /** @since 3.6 */ + public interface FileChooserCallback { + boolean approve( String[] files, long hwndFileDialog ); + } + + /** + * Shows a Windows message box + * MessageBox. + *

+ * For use in {@link FileChooserCallback} only. + * + * @param hwndParent the parent of the message box + * @param text message to be displayed + * @param caption dialog box title + * @param type see MessageBox parameter uType + * @return see MessageBox Return value + * @return index of pressed button; or -1 for ESC key + * + * @since 3.6 + */ + public native static int showMessageDialog( long hwndParent, + String text, String caption, int type ); } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/SystemFileChooser.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/SystemFileChooser.java index 6801014e..b4528f62 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/SystemFileChooser.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/SystemFileChooser.java @@ -450,7 +450,7 @@ public class SystemFileChooser new Thread( () -> { filenamesRef.set( showSystemDialog( owner, fc ) ); secondaryLoop.exit(); - } ).start(); + }, "FlatLaf SystemFileChooser" ).start(); secondaryLoop.enter(); String[] filenames = filenamesRef.get(); @@ -552,7 +552,7 @@ public class SystemFileChooser // show system file dialog return FlatNativeWindowsLibrary.showFileChooser( owner, open, fc.getDialogTitle(), approveButtonText, null, fileName, - folder, saveAsItem, null, null, optionsSet, optionsClear, + folder, saveAsItem, null, null, optionsSet, optionsClear, null, fileTypeIndex, fileTypes.toArray( new String[fileTypes.size()] ) ); } } @@ -693,7 +693,7 @@ public class SystemFileChooser // show system file dialog return FlatNativeLinuxLibrary.showFileChooser( owner, open, fc.getDialogTitle(), approveButtonText, currentName, currentFolder, - optionsSet, optionsClear, fileTypeIndex, fileTypes.toArray( new String[fileTypes.size()] ) ); + optionsSet, optionsClear, null, fileTypeIndex, fileTypes.toArray( new String[fileTypes.size()] ) ); } private String caseInsensitiveGlobPattern( String ext ) { diff --git a/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/GtkFileChooser.cpp b/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/GtkFileChooser.cpp index 567c485c..7e427a85 100644 --- a/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/GtkFileChooser.cpp +++ b/flatlaf-natives/flatlaf-natives-linux/src/main/cpp/GtkFileChooser.cpp @@ -29,6 +29,9 @@ // 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 ); + //---- class AutoReleaseStringUTF8 -------------------------------------------- class AutoReleaseStringUTF8 { @@ -142,10 +145,37 @@ static void handle_realize( GtkWidget* dialog, gpointer data ) { 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 ) - *((GSList**)data) = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER( dialog ) ); + if( responseId == GTK_RESPONSE_ACCEPT ) { + ResponseData *response = static_cast( 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 ); @@ -159,7 +189,7 @@ 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, jint fileTypeIndex, jobjectArray fileTypes ) + jint optionsSet, jint optionsClear, jobject callback, jint fileTypeIndex, jobjectArray fileTypes ) { // initialize GTK if( !gtk_init_check( NULL, NULL ) ) @@ -222,8 +252,8 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrar // 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 ); + 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) @@ -241,10 +271,14 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrar gtk_main(); // canceled? - if( fileList == NULL ) + 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; @@ -259,3 +293,52 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrar g_slist_free( fileList ); return array; } + + +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; +} diff --git a/flatlaf-natives/flatlaf-natives-linux/src/main/headers/com_formdev_flatlaf_ui_FlatNativeLinuxLibrary.h b/flatlaf-natives/flatlaf-natives-linux/src/main/headers/com_formdev_flatlaf_ui_FlatNativeLinuxLibrary.h index 4ca717bb..5e9573b4 100644 --- a/flatlaf-natives/flatlaf-natives-linux/src/main/headers/com_formdev_flatlaf_ui_FlatNativeLinuxLibrary.h +++ b/flatlaf-natives/flatlaf-natives-linux/src/main/headers/com_formdev_flatlaf_ui_FlatNativeLinuxLibrary.h @@ -40,10 +40,18 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_xS /* * Class: com_formdev_flatlaf_ui_FlatNativeLinuxLibrary * Method: showFileChooser - * Signature: (Ljava/awt/Window;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;III[Ljava/lang/String;)[Ljava/lang/String; + * 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, jint, jobjectArray); + (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 } diff --git a/flatlaf-natives/flatlaf-natives-windows/build.gradle.kts b/flatlaf-natives/flatlaf-natives-windows/build.gradle.kts index 3dc0a1dc..a0893c8c 100644 --- a/flatlaf-natives/flatlaf-natives-windows/build.gradle.kts +++ b/flatlaf-natives/flatlaf-natives-windows/build.gradle.kts @@ -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() } } ) @@ -81,7 +81,7 @@ tasks { linkerArgs.addAll( toolChain.map { when( it ) { 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", "/NODEFAULTLIB" ) + is VisualCpp -> listOf( "User32.lib", "Gdi32.lib", "shell32.lib", "AdvAPI32.lib", "Kernel32.lib", "Dwmapi.lib", "Ole32.lib", "uuid.lib" ) else -> emptyList() } } ) diff --git a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/WinFileChooser.cpp b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/WinFileChooser.cpp index 8b300d34..2a643643 100644 --- a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/WinFileChooser.cpp +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/WinFileChooser.cpp @@ -29,6 +29,9 @@ // 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 AutoReleasePtr { @@ -38,6 +41,10 @@ public: AutoReleasePtr() { ptr = NULL; } + AutoReleasePtr( T* p ) { + ptr = p; + ptr->AddRef(); + } ~AutoReleasePtr() { if( ptr != NULL ) ptr->Release(); @@ -126,6 +133,86 @@ public: } }; +//---- 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 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 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( 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 { @@ -163,7 +250,7 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibr ( 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, jint fileTypeIndex, jobjectArray fileTypes ) + jint optionsSet, jint optionsClear, jobject callback, jint fileTypeIndex, jobjectArray fileTypes ) { // initialize COM library CoInitializer coInitializer; @@ -226,20 +313,31 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibr CHECK_HRESULT( dialog->SetFileTypeIndex( min( fileTypeIndex + 1, specs.count ) ) ); } + // add event handler + AutoReleasePtr 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 ); - // convert shell items to Java string array + // get selected files as Java string array + return getFiles( env, open, dialog ); +} + +static jobjectArray getFiles( JNIEnv* env, jboolean open, IFileDialog* dialog ) { if( open ) { AutoReleasePtr 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 shellItem; @@ -260,6 +358,7 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibr 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 ); @@ -270,3 +369,15 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibr return array; } } + + +extern "C" +JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_showMessageDialog + ( 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( hwndParent ), ctext, ccaption, type ); +} diff --git a/flatlaf-natives/flatlaf-natives-windows/src/main/headers/com_formdev_flatlaf_ui_FlatNativeWindowsLibrary.h b/flatlaf-natives/flatlaf-natives-windows/src/main/headers/com_formdev_flatlaf_ui_FlatNativeWindowsLibrary.h index 895b7267..18b9f377 100644 --- a/flatlaf-natives/flatlaf-natives-windows/src/main/headers/com_formdev_flatlaf_ui_FlatNativeWindowsLibrary.h +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/headers/com_formdev_flatlaf_ui_FlatNativeWindowsLibrary.h @@ -116,10 +116,18 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_ /* * 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;III[Ljava/lang/String;)[Ljava/lang/String; + * 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, jint, jobjectArray); + (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: (JLjava/lang/String;Ljava/lang/String;I)I + */ +JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_showMessageDialog + (JNIEnv *, jclass, jlong, jstring, jstring, jint); #ifdef __cplusplus } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserLinuxTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserLinuxTest.java index 05289d16..0c4f59f6 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserLinuxTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserLinuxTest.java @@ -124,10 +124,20 @@ public class FlatSystemFileChooserLinuxTest } int fileTypeIndex = fileTypeIndexSlider.getValue(); + FlatNativeLinuxLibrary.FileChooserCallback callback = (files, hwndFileDialog) -> { + System.out.println( " -- callback " + hwndFileDialog + " " + Arrays.toString( files ) ); + if( showMessageDialogOnOKCheckBox.isSelected() ) { + System.out.println( FlatNativeLinuxLibrary.showMessageDialog( hwndFileDialog, + JOptionPane.INFORMATION_MESSAGE, + "primary text", "secondary text", 1, "Yes", "No" ) ); + } + return true; + }; + if( direct ) { String[] files = FlatNativeLinuxLibrary.showFileChooser( owner, open, title, okButtonLabel, currentName, currentFolder, - optionsSet.get(), optionsClear.get(), fileTypeIndex, fileTypes ); + optionsSet.get(), optionsClear.get(), callback, fileTypeIndex, fileTypes ); filesField.setText( (files != null) ? Arrays.toString( files ).replace( ',', '\n' ) : "null" ); } else { @@ -137,7 +147,7 @@ public class FlatSystemFileChooserLinuxTest new Thread( () -> { String[] files = FlatNativeLinuxLibrary.showFileChooser( owner, open, title, okButtonLabel, currentName, currentFolder, - optionsSet.get(), optionsClear.get(), fileTypeIndex, fileTypes2 ); + optionsSet.get(), optionsClear.get(), callback, fileTypeIndex, fileTypes2 ); System.out.println( " secondaryLoop.exit() returned " + secondaryLoop.exit() ); @@ -248,6 +258,7 @@ public class FlatSystemFileChooserLinuxTest saveButton = new JButton(); openDirectButton = new JButton(); saveDirectButton = new JButton(); + showMessageDialogOnOKCheckBox = new JCheckBox(); filesScrollPane = new JScrollPane(); filesField = new JTextArea(); @@ -397,6 +408,10 @@ public class FlatSystemFileChooserLinuxTest saveDirectButton.addActionListener(e -> saveDirect()); add(saveDirectButton, "cell 0 7 3 1"); + //---- showMessageDialogOnOKCheckBox ---- + showMessageDialogOnOKCheckBox.setText("show message dialog on OK"); + add(showMessageDialogOnOKCheckBox, "cell 0 7 3 1"); + //======== filesScrollPane ======== { @@ -443,6 +458,7 @@ public class FlatSystemFileChooserLinuxTest private JButton saveButton; private JButton openDirectButton; private JButton saveDirectButton; + private JCheckBox showMessageDialogOnOKCheckBox; private JScrollPane filesScrollPane; private JTextArea filesField; // JFormDesigner - End of variables declaration //GEN-END:variables diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserLinuxTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserLinuxTest.jfd index d172fc00..3506768d 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserLinuxTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserLinuxTest.jfd @@ -1,4 +1,4 @@ -JFDML JFormDesigner: "8.3" encoding: "UTF-8" +JFDML JFormDesigner: "8.2.2.0.9999" Java: "21.0.1" encoding: "UTF-8" new FormModel { contentType: "form/swing" @@ -198,6 +198,12 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 7 3 1" } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "showMessageDialogOnOKCheckBox" + "text": "show message dialog on OK" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 7 3 1" + } ) add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) { name: "filesScrollPane" add( new FormComponent( "javax.swing.JTextArea" ) { diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.java index 2aa9af1e..621d00d8 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.java @@ -139,11 +139,21 @@ public class FlatSystemFileChooserWindowsTest fileTypes = fileTypesStr.trim().split( "[,]+" ); int fileTypeIndex = fileTypeIndexSlider.getValue(); + FlatNativeWindowsLibrary.FileChooserCallback callback = (files, hwndFileDialog) -> { + System.out.println( " -- callback " + hwndFileDialog + " " + Arrays.toString( files ) ); + if( showMessageDialogOnOKCheckBox.isSelected() ) { + System.out.println( FlatNativeWindowsLibrary.showMessageDialog( hwndFileDialog, + "some text", "title", + /* MB_ICONINFORMATION */ 0x00000040 | /* MB_YESNO */ 0x00000004 ) ); + } + return true; + }; + if( direct ) { String[] files = FlatNativeWindowsLibrary.showFileChooser( owner, open, title, okButtonLabel, fileNameLabel, fileName, folder, saveAsItem, defaultFolder, defaultExtension, - optionsSet.get(), optionsClear.get(), fileTypeIndex, fileTypes ); + optionsSet.get(), optionsClear.get(), callback, fileTypeIndex, fileTypes ); filesField.setText( (files != null) ? Arrays.toString( files ).replace( ',', '\n' ) : "null" ); } else { @@ -154,7 +164,7 @@ public class FlatSystemFileChooserWindowsTest String[] files = FlatNativeWindowsLibrary.showFileChooser( owner, open, title, okButtonLabel, fileNameLabel, fileName, folder, saveAsItem, defaultFolder, defaultExtension, - optionsSet.get(), optionsClear.get(), fileTypeIndex, fileTypes2 ); + optionsSet.get(), optionsClear.get(), callback, fileTypeIndex, fileTypes2 ); System.out.println( " secondaryLoop.exit() returned " + secondaryLoop.exit() ); @@ -290,6 +300,7 @@ public class FlatSystemFileChooserWindowsTest saveButton = new JButton(); openDirectButton = new JButton(); saveDirectButton = new JButton(); + showMessageDialogOnOKCheckBox = new JCheckBox(); filesScrollPane = new JScrollPane(); filesField = new JTextArea(); @@ -534,6 +545,10 @@ public class FlatSystemFileChooserWindowsTest saveDirectButton.addActionListener(e -> saveDirect()); add(saveDirectButton, "cell 0 11 3 1"); + //---- showMessageDialogOnOKCheckBox ---- + showMessageDialogOnOKCheckBox.setText("show message dialog on OK"); + add(showMessageDialogOnOKCheckBox, "cell 0 11 3 1"); + //======== filesScrollPane ======== { @@ -605,6 +620,7 @@ public class FlatSystemFileChooserWindowsTest private JButton saveButton; private JButton openDirectButton; private JButton saveDirectButton; + private JCheckBox showMessageDialogOnOKCheckBox; private JScrollPane filesScrollPane; private JTextArea filesField; // JFormDesigner - End of variables declaration //GEN-END:variables diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.jfd index 82828444..74e22930 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserWindowsTest.jfd @@ -343,6 +343,12 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 11 3 1" } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "showMessageDialogsOnOKCheckBox" + "text": "show message dialog on OK" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 11 3 1" + } ) add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) { name: "filesScrollPane" add( new FormComponent( "javax.swing.JTextArea" ) {