System File Chooser: support "approve" callback and system message dialog on Windows and Linux (not yet used in SystemFileChooser

This commit is contained in:
Karl Tauber
2025-01-11 17:50:46 +01:00
parent c73fd51704
commit d49282dfe8
12 changed files with 335 additions and 25 deletions

View File

@@ -24,6 +24,7 @@ import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
import javax.swing.JDialog; import javax.swing.JDialog;
import javax.swing.JFrame; import javax.swing.JFrame;
import javax.swing.JOptionPane;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
/** /**
@@ -131,7 +132,7 @@ public class FlatNativeLinuxLibrary
FC_create_folders = 1 << 5; // default for Save FC_create_folders = 1 << 5; // default for Save
/** /**
* Shows the Linux system file dialog * Shows the Linux/GTK system file dialog
* <a href="https://docs.gtk.org/gtk3/class.FileChooserDialog.html">GtkFileChooserDialog</a>. * <a href="https://docs.gtk.org/gtk3/class.FileChooserDialog.html">GtkFileChooserDialog</a>.
* <p> * <p>
* Uses {@code GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER} if {@link #FC_select_folder} is set in parameter {@code optionsSet}. * 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 currentFolder current directory shown in the dialog; or {@code null}
* @param optionsSet options to set; see {@code FOS_*} constants * @param optionsSet options to set; see {@code FOS_*} constants
* @param optionsClear options to clear; 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 fileTypeIndex the file type that appears as selected (zero-based)
* @param fileTypes file types that the dialog can open or save. * @param fileTypes file types that the dialog can open or save.
* Two or more strings and {@code null} are required for each filter. * 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, public native static String[] showFileChooser( Window owner, boolean open,
String title, String okButtonLabel, String currentName, String currentFolder, 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
* <a href="https://docs.gtk.org/gtk3/class.MessageDialog.html">GtkMessageDialog</a>.
* <p>
* 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 );
} }

View File

@@ -218,6 +218,7 @@ public class FlatNativeWindowsLibrary
* @param defaultExtension default extension to be added to file name in save dialog; or {@code null} * @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 optionsSet options to set; see {@code FOS_*} constants
* @param optionsClear options to clear; 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 fileTypeIndex the file type that appears as selected (zero-based)
* @param fileTypes file types that the dialog can open or save. * @param fileTypes file types that the dialog can open or save.
* Pairs of strings are required for each filter. * Pairs of strings are required for each filter.
@@ -231,5 +232,29 @@ public class FlatNativeWindowsLibrary
public native static String[] showFileChooser( Window owner, boolean open, public native static String[] showFileChooser( Window owner, boolean open,
String title, String okButtonLabel, String fileNameLabel, String fileName, String title, String okButtonLabel, String fileNameLabel, String fileName,
String folder, String saveAsItem, String defaultFolder, String defaultExtension, 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
* <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox">MessageBox</a>.
* <p>
* 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 <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox#parameters">MessageBox parameter uType</a>
* @return see <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox#return-value">MessageBox Return value</a>
* @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 );
} }

View File

@@ -450,7 +450,7 @@ public class SystemFileChooser
new Thread( () -> { new Thread( () -> {
filenamesRef.set( showSystemDialog( owner, fc ) ); filenamesRef.set( showSystemDialog( owner, fc ) );
secondaryLoop.exit(); secondaryLoop.exit();
} ).start(); }, "FlatLaf SystemFileChooser" ).start();
secondaryLoop.enter(); secondaryLoop.enter();
String[] filenames = filenamesRef.get(); String[] filenames = filenamesRef.get();
@@ -552,7 +552,7 @@ public class SystemFileChooser
// show system file dialog // show system file dialog
return FlatNativeWindowsLibrary.showFileChooser( owner, open, return FlatNativeWindowsLibrary.showFileChooser( owner, open,
fc.getDialogTitle(), approveButtonText, null, fileName, 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()] ) ); fileTypeIndex, fileTypes.toArray( new String[fileTypes.size()] ) );
} }
} }
@@ -693,7 +693,7 @@ public class SystemFileChooser
// show system file dialog // show system file dialog
return FlatNativeLinuxLibrary.showFileChooser( owner, open, return FlatNativeLinuxLibrary.showFileChooser( owner, open,
fc.getDialogTitle(), approveButtonText, currentName, currentFolder, 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 ) { private String caseInsensitiveGlobPattern( String ext ) {

View File

@@ -29,6 +29,9 @@
// declare external methods // declare external methods
extern Window getWindowHandle( JNIEnv* env, JAWT* awt, jobject window, Display** display_return ); 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 --------------------------------------------
class AutoReleaseStringUTF8 { class AutoReleaseStringUTF8 {
@@ -142,10 +145,37 @@ static void handle_realize( GtkWidget* dialog, gpointer data ) {
g_object_unref( gdkOwner ); 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 ) { static void handle_response( GtkWidget* dialog, gint responseId, gpointer data ) {
// get filenames if user pressed OK // get filenames if user pressed OK
if( responseId == GTK_RESPONSE_ACCEPT ) if( responseId == GTK_RESPONSE_ACCEPT ) {
*((GSList**)data) = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER( dialog ) ); 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 // hide/destroy file dialog and quit loop
gtk_widget_hide( dialog ); gtk_widget_hide( dialog );
@@ -159,7 +189,7 @@ extern "C"
JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_showFileChooser JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_showFileChooser
( JNIEnv* env, jclass cls, jobject owner, jboolean open, ( JNIEnv* env, jclass cls, jobject owner, jboolean open,
jstring title, jstring okButtonLabel, jstring currentName, jstring currentFolder, 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 // initialize GTK
if( !gtk_init_check( NULL, NULL ) ) if( !gtk_init_check( NULL, NULL ) )
@@ -222,8 +252,8 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrar
// show dialog // show dialog
// (similar to what's done in sun_awt_X11_GtkFileDialogPeer.c) // (similar to what's done in sun_awt_X11_GtkFileDialogPeer.c)
GSList* fileList = NULL; ResponseData responseData( env, callback );
g_signal_connect( dialog, "response", G_CALLBACK( handle_response ), &fileList ); g_signal_connect( dialog, "response", G_CALLBACK( handle_response ), &responseData );
gtk_widget_show( dialog ); gtk_widget_show( dialog );
// necessary to bring file dialog to the front (and make it active) // 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(); gtk_main();
// canceled? // canceled?
if( fileList == NULL ) if( responseData.fileList == NULL )
return newJavaStringArray( env, 0 ); return newJavaStringArray( env, 0 );
// convert GSList to Java string array // convert GSList to Java string array
return fileListToStringArray( env, responseData.fileList );
}
static jobjectArray fileListToStringArray( JNIEnv* env, GSList* fileList ) {
guint count = g_slist_length( fileList ); guint count = g_slist_length( fileList );
jobjectArray array = newJavaStringArray( env, count ); jobjectArray array = newJavaStringArray( env, count );
GSList* it = fileList; GSList* it = fileList;
@@ -259,3 +293,52 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrar
g_slist_free( fileList ); g_slist_free( fileList );
return array; 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;
}

View File

@@ -40,10 +40,18 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_xS
/* /*
* Class: com_formdev_flatlaf_ui_FlatNativeLinuxLibrary * Class: com_formdev_flatlaf_ui_FlatNativeLinuxLibrary
* Method: showFileChooser * Method: showFileChooser
* Signature: (Ljava/awt/Window;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;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 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 #ifdef __cplusplus
} }

View File

@@ -64,7 +64,7 @@ tasks {
compilerArgs.addAll( toolChain.map { compilerArgs.addAll( toolChain.map {
when( it ) { when( it ) {
is Gcc, is Clang -> listOf( "-O2", "-DUNICODE" ) is Gcc, is Clang -> listOf( "-O2", "-DUNICODE" )
is VisualCpp -> listOf( "/O2", "/Zl", "/GS-", "/DUNICODE" ) is VisualCpp -> listOf( "/O2", "/GS-", "/DUNICODE" )
else -> emptyList() else -> emptyList()
} }
} ) } )
@@ -81,7 +81,7 @@ tasks {
linkerArgs.addAll( toolChain.map { linkerArgs.addAll( toolChain.map {
when( it ) { when( it ) {
is Gcc, is Clang -> listOf( "-lUser32", "-lGdi32", "-lshell32", "-lAdvAPI32", "-lKernel32", "-lDwmapi", "-lOle32", "-luuid" ) 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() else -> emptyList()
} }
} ) } )

View File

@@ -29,6 +29,9 @@
// declare external methods // declare external methods
extern HWND getWindowHandle( JNIEnv* env, jobject window ); extern HWND getWindowHandle( JNIEnv* env, jobject window );
// declare internal methods
static jobjectArray getFiles( JNIEnv* env, jboolean open, IFileDialog* dialog );
//---- class AutoReleasePtr --------------------------------------------------- //---- class AutoReleasePtr ---------------------------------------------------
template<class T> class AutoReleasePtr { template<class T> class AutoReleasePtr {
@@ -38,6 +41,10 @@ public:
AutoReleasePtr() { AutoReleasePtr() {
ptr = NULL; ptr = NULL;
} }
AutoReleasePtr( T* p ) {
ptr = p;
ptr->AddRef();
}
~AutoReleasePtr() { ~AutoReleasePtr() {
if( ptr != NULL ) if( ptr != NULL )
ptr->Release(); 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<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 ----------------------------------------------------
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, ( JNIEnv* env, jclass cls, jobject owner, jboolean open,
jstring title, jstring okButtonLabel, jstring fileNameLabel, jstring fileName, jstring title, jstring okButtonLabel, jstring fileNameLabel, jstring fileName,
jstring folder, jstring saveAsItem, jstring defaultFolder, jstring defaultExtension, 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 // initialize COM library
CoInitializer coInitializer; CoInitializer coInitializer;
@@ -226,20 +313,31 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibr
CHECK_HRESULT( dialog->SetFileTypeIndex( min( fileTypeIndex + 1, specs.count ) ) ); 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 // show dialog
HWND hwndOwner = (owner != NULL) ? getWindowHandle( env, owner ) : NULL; HWND hwndOwner = (owner != NULL) ? getWindowHandle( env, owner ) : NULL;
HRESULT hr = dialog->Show( hwndOwner ); HRESULT hr = dialog->Show( hwndOwner );
dialog->Unadvise( dwCookie );
if( hr == HRESULT_FROM_WIN32(ERROR_CANCELLED) ) if( hr == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
return newJavaStringArray( env, 0 ); return newJavaStringArray( env, 0 );
CHECK_HRESULT( hr ); 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 ) { if( open ) {
AutoReleasePtr<IShellItemArray> shellItems; AutoReleasePtr<IShellItemArray> shellItems;
DWORD count; DWORD count;
CHECK_HRESULT( ((IFileOpenDialog*)(IFileDialog*)dialog)->GetResults( &shellItems ) ); CHECK_HRESULT( ((IFileOpenDialog*)(IFileDialog*)dialog)->GetResults( &shellItems ) );
CHECK_HRESULT( shellItems->GetCount( &count ) ); CHECK_HRESULT( shellItems->GetCount( &count ) );
// convert shell items to Java string array
jobjectArray array = newJavaStringArray( env, count ); jobjectArray array = newJavaStringArray( env, count );
for( int i = 0; i < count; i++ ) { for( int i = 0; i < count; i++ ) {
AutoReleasePtr<IShellItem> shellItem; AutoReleasePtr<IShellItem> shellItem;
@@ -260,6 +358,7 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibr
CHECK_HRESULT( dialog->GetResult( &shellItem ) ); CHECK_HRESULT( dialog->GetResult( &shellItem ) );
CHECK_HRESULT( shellItem->GetDisplayName( SIGDN_FILESYSPATH, &path ) ); CHECK_HRESULT( shellItem->GetDisplayName( SIGDN_FILESYSPATH, &path ) );
// convert shell item to Java string array
jstring jpath = newJavaString( env, path ); jstring jpath = newJavaString( env, path );
CoTaskMemFree( path ); CoTaskMemFree( path );
@@ -270,3 +369,15 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibr
return array; 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<HWND>( hwndParent ), ctext, ccaption, type );
}

View File

@@ -116,10 +116,18 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_
/* /*
* Class: com_formdev_flatlaf_ui_FlatNativeWindowsLibrary * Class: com_formdev_flatlaf_ui_FlatNativeWindowsLibrary
* Method: showFileChooser * 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 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 #ifdef __cplusplus
} }

View File

@@ -124,10 +124,20 @@ public class FlatSystemFileChooserLinuxTest
} }
int fileTypeIndex = fileTypeIndexSlider.getValue(); 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 ) { if( direct ) {
String[] files = FlatNativeLinuxLibrary.showFileChooser( owner, open, String[] files = FlatNativeLinuxLibrary.showFileChooser( owner, open,
title, okButtonLabel, currentName, currentFolder, 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" ); filesField.setText( (files != null) ? Arrays.toString( files ).replace( ',', '\n' ) : "null" );
} else { } else {
@@ -137,7 +147,7 @@ public class FlatSystemFileChooserLinuxTest
new Thread( () -> { new Thread( () -> {
String[] files = FlatNativeLinuxLibrary.showFileChooser( owner, open, String[] files = FlatNativeLinuxLibrary.showFileChooser( owner, open,
title, okButtonLabel, currentName, currentFolder, 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() ); System.out.println( " secondaryLoop.exit() returned " + secondaryLoop.exit() );
@@ -248,6 +258,7 @@ public class FlatSystemFileChooserLinuxTest
saveButton = new JButton(); saveButton = new JButton();
openDirectButton = new JButton(); openDirectButton = new JButton();
saveDirectButton = new JButton(); saveDirectButton = new JButton();
showMessageDialogOnOKCheckBox = new JCheckBox();
filesScrollPane = new JScrollPane(); filesScrollPane = new JScrollPane();
filesField = new JTextArea(); filesField = new JTextArea();
@@ -397,6 +408,10 @@ public class FlatSystemFileChooserLinuxTest
saveDirectButton.addActionListener(e -> saveDirect()); saveDirectButton.addActionListener(e -> saveDirect());
add(saveDirectButton, "cell 0 7 3 1"); add(saveDirectButton, "cell 0 7 3 1");
//---- showMessageDialogOnOKCheckBox ----
showMessageDialogOnOKCheckBox.setText("show message dialog on OK");
add(showMessageDialogOnOKCheckBox, "cell 0 7 3 1");
//======== filesScrollPane ======== //======== filesScrollPane ========
{ {
@@ -443,6 +458,7 @@ public class FlatSystemFileChooserLinuxTest
private JButton saveButton; private JButton saveButton;
private JButton openDirectButton; private JButton openDirectButton;
private JButton saveDirectButton; private JButton saveDirectButton;
private JCheckBox showMessageDialogOnOKCheckBox;
private JScrollPane filesScrollPane; private JScrollPane filesScrollPane;
private JTextArea filesField; private JTextArea filesField;
// JFormDesigner - End of variables declaration //GEN-END:variables // JFormDesigner - End of variables declaration //GEN-END:variables

View File

@@ -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 { new FormModel {
contentType: "form/swing" contentType: "form/swing"
@@ -198,6 +198,12 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 7 3 1" "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 ) ) { add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
name: "filesScrollPane" name: "filesScrollPane"
add( new FormComponent( "javax.swing.JTextArea" ) { add( new FormComponent( "javax.swing.JTextArea" ) {

View File

@@ -139,11 +139,21 @@ public class FlatSystemFileChooserWindowsTest
fileTypes = fileTypesStr.trim().split( "[,]+" ); fileTypes = fileTypesStr.trim().split( "[,]+" );
int fileTypeIndex = fileTypeIndexSlider.getValue(); 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 ) { if( direct ) {
String[] files = FlatNativeWindowsLibrary.showFileChooser( owner, open, String[] files = FlatNativeWindowsLibrary.showFileChooser( owner, open,
title, okButtonLabel, fileNameLabel, fileName, title, okButtonLabel, fileNameLabel, fileName,
folder, saveAsItem, defaultFolder, defaultExtension, 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" ); filesField.setText( (files != null) ? Arrays.toString( files ).replace( ',', '\n' ) : "null" );
} else { } else {
@@ -154,7 +164,7 @@ public class FlatSystemFileChooserWindowsTest
String[] files = FlatNativeWindowsLibrary.showFileChooser( owner, open, String[] files = FlatNativeWindowsLibrary.showFileChooser( owner, open,
title, okButtonLabel, fileNameLabel, fileName, title, okButtonLabel, fileNameLabel, fileName,
folder, saveAsItem, defaultFolder, defaultExtension, 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() ); System.out.println( " secondaryLoop.exit() returned " + secondaryLoop.exit() );
@@ -290,6 +300,7 @@ public class FlatSystemFileChooserWindowsTest
saveButton = new JButton(); saveButton = new JButton();
openDirectButton = new JButton(); openDirectButton = new JButton();
saveDirectButton = new JButton(); saveDirectButton = new JButton();
showMessageDialogOnOKCheckBox = new JCheckBox();
filesScrollPane = new JScrollPane(); filesScrollPane = new JScrollPane();
filesField = new JTextArea(); filesField = new JTextArea();
@@ -534,6 +545,10 @@ public class FlatSystemFileChooserWindowsTest
saveDirectButton.addActionListener(e -> saveDirect()); saveDirectButton.addActionListener(e -> saveDirect());
add(saveDirectButton, "cell 0 11 3 1"); add(saveDirectButton, "cell 0 11 3 1");
//---- showMessageDialogOnOKCheckBox ----
showMessageDialogOnOKCheckBox.setText("show message dialog on OK");
add(showMessageDialogOnOKCheckBox, "cell 0 11 3 1");
//======== filesScrollPane ======== //======== filesScrollPane ========
{ {
@@ -605,6 +620,7 @@ public class FlatSystemFileChooserWindowsTest
private JButton saveButton; private JButton saveButton;
private JButton openDirectButton; private JButton openDirectButton;
private JButton saveDirectButton; private JButton saveDirectButton;
private JCheckBox showMessageDialogOnOKCheckBox;
private JScrollPane filesScrollPane; private JScrollPane filesScrollPane;
private JTextArea filesField; private JTextArea filesField;
// JFormDesigner - End of variables declaration //GEN-END:variables // JFormDesigner - End of variables declaration //GEN-END:variables

View File

@@ -343,6 +343,12 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 11 3 1" "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 ) ) { add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
name: "filesScrollPane" name: "filesScrollPane"
add( new FormComponent( "javax.swing.JTextArea" ) { add( new FormComponent( "javax.swing.JTextArea" ) {