diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java
index 9308a680..362f8ee3 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java
@@ -18,6 +18,7 @@ package com.formdev.flatlaf.ui;
import java.awt.Rectangle;
import java.awt.Window;
+import javax.swing.JOptionPane;
import com.formdev.flatlaf.util.SystemInfo;
/**
@@ -122,5 +123,32 @@ public class FlatNativeMacLibrary
public native static String[] showFileChooser( boolean open,
String title, String prompt, String message, String filterFieldLabel,
String nameFieldLabel, String nameFieldStringValue, String directoryURL,
- 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 macOS alert
+ * NSAlert.
+ *
+ * For use in {@link FileChooserCallback} only.
+ *
+ * @param hwndParent the parent of the message box
+ * @param alertStyle type of alert being displayed:
+ * {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#INFORMATION_MESSAGE} or
+ * {@link JOptionPane#WARNING_MESSAGE}
+ * @param messageText main message of the alert
+ * @param informativeText additional information about the alert; shown below of main message; 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
+ *
+ * @since 3.6
+ */
+ public native static int showMessageDialog( long hwndParent, int alertStyle,
+ String messageText, String informativeText, int defaultButton, String... buttons );
}
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 b4528f62..f3740102 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
@@ -616,8 +616,8 @@ public class SystemFileChooser
// show system file dialog
return FlatNativeMacLibrary.showFileChooser( open,
fc.getDialogTitle(), fc.getApproveButtonText(), null, null, null,
- nameFieldStringValue, directoryURL,
- optionsSet, optionsClear, fileTypeIndex, fileTypes.toArray( new String[fileTypes.size()] ) );
+ nameFieldStringValue, directoryURL, optionsSet, optionsClear, null,
+ fileTypeIndex, fileTypes.toArray( new String[fileTypes.size()] ) );
}
}
diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/headers/JNIUtils.h b/flatlaf-natives/flatlaf-natives-macos/src/main/headers/JNIUtils.h
index 36b1bd4c..b2dfe674 100644
--- a/flatlaf-natives/flatlaf-natives-macos/src/main/headers/JNIUtils.h
+++ b/flatlaf-natives/flatlaf-natives-macos/src/main/headers/JNIUtils.h
@@ -29,15 +29,43 @@
#endif
+#define JNI_COCOA_TRY() \
+ @try {
+
+#define JNI_COCOA_CATCH() \
+ } @catch( NSException *ex ) { \
+ NSLog( @"Exception: %@\nReason: %@\nUser Info: %@\nStack: %@", \
+ [ex name], [ex reason], [ex userInfo], [ex callStackSymbols] ); \
+ }
+
#define JNI_COCOA_ENTER() \
@autoreleasepool { \
- @try {
+ JNI_COCOA_TRY()
#define JNI_COCOA_EXIT() \
- } @catch( NSException *ex ) { \
- NSLog( @"Exception: %@\nReason: %@\nUser Info: %@\nStack: %@", \
- [ex name], [ex reason], [ex userInfo], [ex callStackSymbols] ); \
- } \
+ 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(); \
}
diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h b/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h
index 1e581622..329085b0 100644
--- a/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h
+++ b/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h
@@ -82,10 +82,18 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_togg
/*
* Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary
* Method: showFileChooser
- * Signature: (ZLjava/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: (ZLjava/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, jboolean, jstring, jstring, jstring, jstring, jstring, jstring, jstring, jint, jint, jint, jobjectArray);
+ (JNIEnv *, jclass, 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
}
diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacFileChooser.mm b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacFileChooser.mm
index d07caec2..a782ca74 100644
--- a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacFileChooser.mm
+++ b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacFileChooser.mm
@@ -26,10 +26,19 @@
* @since 3.6
*/
+// 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 {
+@interface FileChooserDelegate : NSObject {
NSArray* _filters;
+
+ JavaVM* _jvm;
+ jobject _callback;
+ NSMutableSet* _urlsSet;
}
@property (nonatomic, assign) NSSavePanel* dialog;
@@ -118,6 +127,53 @@
_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()
+
+ 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;
+ }
+
@end
//---- helper -----------------------------------------------------------------
@@ -126,9 +182,6 @@
#define isOptionClear( option ) ((optionsClear & com_formdev_flatlaf_ui_FlatNativeMacLibrary_ ## option) != 0)
#define isOptionSetOrClear( option ) isOptionSet( option ) || isOptionClear( option )
-// declare external methods
-extern NSWindow* getNSWindow( JNIEnv* env, jclass cls, jobject window );
-
static jobjectArray newJavaStringArray( JNIEnv* env, jsize count ) {
jclass stringClass = env->FindClass( "java/lang/String" );
return env->NewObjectArray( count, stringClass, NULL );
@@ -182,10 +235,14 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_
( JNIEnv* env, jclass cls, jboolean open,
jstring title, jstring prompt, jstring message, jstring filterFieldLabel,
jstring nameFieldLabel, jstring nameFieldStringValue, jstring directoryURL,
- jint optionsSet, jint optionsClear, jint fileTypeIndex, jobjectArray fileTypes )
+ 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 );
@@ -198,11 +255,11 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_
NSArray* urls = NULL;
NSArray** purls = &urls;
- NSURL* url = NULL;
- NSURL** purl = &url;
// show file dialog on macOS thread
[FlatJNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){
+ JNI_COCOA_TRY()
+
NSSavePanel* dialog = open ? [NSOpenPanel openPanel] : [NSSavePanel savePanel];
if( nsTitle != NULL )
@@ -262,35 +319,108 @@ JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_
((NSOpenPanel*)dialog).accessoryViewDisclosed = isOptionSet( FC_accessoryViewDisclosed );
}
+ // initialize callback
+ if( callback != NULL ) {
+ [delegate initCallback :jvm :callback];
+ dialog.delegate = delegate;
+ }
+
// show dialog
NSModalResponse response = [dialog runModal];
[delegate release];
- if( response != NSModalResponseOK )
+ if( response != NSModalResponseOK ) {
+ *purls = @[];
return;
+ }
- if( open )
- *purls = ((NSOpenPanel*)dialog).URLs;
- else
- *purl = dialog.URL;
+ *purls = getDialogURLs( dialog );
+
+ JNI_COCOA_CATCH()
}];
- if( url != NULL )
- urls = @[url];
-
if( urls == NULL )
- return newJavaStringArray( env, 0 );
+ return NULL;
// convert URLs to Java string array
- jsize count = urls.count;
+ return urlsToStringArray( env, urls );
+
+ JNI_COCOA_EXIT()
+}
+
+static NSArray* getDialogURLs( NSSavePanel* dialog ) {
+ if( [dialog isKindOfClass:[NSOpenPanel class]] )
+ return static_cast(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;
+}
+
+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 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()
}
-
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserMacTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserMacTest.java
index 99d2297b..de339d50 100644
--- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserMacTest.java
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserMacTest.java
@@ -59,7 +59,7 @@ public class FlatSystemFileChooserMacTest
}
FlatTestFrame frame = FlatTestFrame.create( args, "FlatSystemFileChooserMacTest" );
- addListeners( frame );
+// addListeners( frame );
frame.showFrame( FlatSystemFileChooserMacTest::new );
} );
}
@@ -133,11 +133,24 @@ public class FlatSystemFileChooserMacTest
}
int fileTypeIndex = fileTypeIndexSlider.getValue();
+ FlatNativeMacLibrary.FileChooserCallback callback = (files, hwndFileDialog) -> {
+ System.out.println( " -- callback " + hwndFileDialog + " " + Arrays.toString( files ) );
+ if( showMessageDialogOnOKCheckBox.isSelected() ) {
+ int result = FlatNativeMacLibrary.showMessageDialog( hwndFileDialog,
+ JOptionPane.INFORMATION_MESSAGE,
+ "primary text", "secondary text", 0, "Yes", "No" );
+ System.out.println( " result " + result );
+ if( result != 0 )
+ return false;
+ }
+ return true;
+ };
+
if( direct ) {
String[] files = FlatNativeMacLibrary.showFileChooser( open,
title, prompt, message, filterFieldLabel,
nameFieldLabel, nameFieldStringValue, directoryURL,
- optionsSet.get(), optionsClear.get(), fileTypeIndex, fileTypes );
+ optionsSet.get(), optionsClear.get(), callback, fileTypeIndex, fileTypes );
filesField.setText( (files != null) ? Arrays.toString( files ).replace( ',', '\n' ) : "null" );
} else {
@@ -148,7 +161,7 @@ public class FlatSystemFileChooserMacTest
String[] files = FlatNativeMacLibrary.showFileChooser( open,
title, prompt, message, filterFieldLabel,
nameFieldLabel, nameFieldStringValue, directoryURL,
- optionsSet.get(), optionsClear.get(), fileTypeIndex, fileTypes2 );
+ optionsSet.get(), optionsClear.get(), callback, fileTypeIndex, fileTypes2 );
System.out.println( " secondaryLoop.exit() returned " + secondaryLoop.exit() );
@@ -173,6 +186,7 @@ public class FlatSystemFileChooserMacTest
optionsClear.set( optionsClear.get() | option );
}
+ @SuppressWarnings( "unused" )
private static void addListeners( Window w ) {
w.addWindowListener( new WindowListener() {
@Override
@@ -270,6 +284,7 @@ public class FlatSystemFileChooserMacTest
saveButton = new JButton();
openDirectButton = new JButton();
saveDirectButton = new JButton();
+ showMessageDialogOnOKCheckBox = new JCheckBox();
filesScrollPane = new JScrollPane();
filesField = new JTextArea();
@@ -468,6 +483,10 @@ public class FlatSystemFileChooserMacTest
saveDirectButton.addActionListener(e -> saveDirect());
add(saveDirectButton, "cell 0 10 3 1");
+ //---- showMessageDialogOnOKCheckBox ----
+ showMessageDialogOnOKCheckBox.setText("show message dialog on OK");
+ add(showMessageDialogOnOKCheckBox, "cell 0 10 3 1");
+
//======== filesScrollPane ========
{
@@ -519,6 +538,7 @@ public class FlatSystemFileChooserMacTest
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/FlatSystemFileChooserMacTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserMacTest.jfd
index 02b47359..83d73f63 100644
--- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserMacTest.jfd
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserMacTest.jfd
@@ -257,6 +257,12 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 10 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 10 3 1"
+ } )
add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
name: "filesScrollPane"
add( new FormComponent( "javax.swing.JTextArea" ) {