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 7d08376a..60343778 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
@@ -44,7 +44,7 @@ import com.formdev.flatlaf.util.SystemInfo;
*/
public class FlatNativeMacLibrary
{
- private static int API_VERSION_MACOS = 2001;
+ private static int API_VERSION_MACOS = 2002;
/**
* Checks whether native library is loaded/available.
@@ -68,4 +68,50 @@ public class FlatNativeMacLibrary
/** @since 3.4 */ public native static Rectangle getWindowButtonsBounds( Window window );
/** @since 3.4 */ public native static boolean isWindowFullScreen( Window window );
/** @since 3.4 */ public native static boolean toggleWindowFullScreen( Window window );
+
+
+ /** @since 3.6 */
+ public static final int
+ // NSOpenPanel
+ FC_canChooseFiles = 1 << 0, // default
+ FC_canChooseDirectories = 1 << 1,
+ FC_resolvesAliases_NO = 1 << 2, // default
+ FC_allowsMultipleSelection = 1 << 3,
+ // NSSavePanel
+ FC_showsTagField_YES = 1 << 8, // default for Save
+ FC_showsTagField_NO = 1 << 9, // default for Open
+ FC_canCreateDirectories_YES = 1 << 10, // default for Save
+ FC_canCreateDirectories_NO = 1 << 11, // default for Open
+ FC_canSelectHiddenExtension = 1 << 12,
+ FC_showsHiddenFiles = 1 << 14,
+ FC_extensionHidden = 1 << 16,
+ FC_allowsOtherFileTypes = 1 << 18,
+ FC_treatsFilePackagesAsDirectories = 1 << 20;
+
+ /**
+ * Shows the macOS system file dialogs
+ * NSOpenPanel or
+ * NSSavePanel.
+ *
+ * Note: This method blocks the current thread until the user closes
+ * the file dialog. It is highly recommended to invoke it from a new thread
+ * to avoid blocking the AWT event dispatching thread.
+ *
+ * @param open if {@code true}, shows the open dialog; if {@code false}, shows the save dialog
+ * @param title text displayed at top of save dialog (not used in open dialog)
+ * @param prompt text displayed in default button
+ * @param message text displayed at top of open/save dialogs
+ * @param nameFieldLabel text displayed in front of the filename text field in save dialog (not used in open dialog)
+ * @param nameFieldStringValue user-editable filename currently shown in the name field in save dialog (not used in open dialog)
+ * @param directoryURL current directory shown in the dialog
+ * @param options see {@code FC_*} constants
+ * @param allowedFileTypes allowed filename extensions (e.g. "txt")
+ * @return file path(s) that the user selected, or {@code null} if canceled
+ *
+ * @since 3.6
+ */
+ public native static String[] showFileChooser( boolean open,
+ String title, String prompt, String message, String nameFieldLabel,
+ String nameFieldStringValue, String directoryURL, int options,
+ String... allowedFileTypes );
}
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 c4b4e58d..36b1bd4c 100644
--- a/flatlaf-natives/flatlaf-natives-macos/src/main/headers/JNIUtils.h
+++ b/flatlaf-natives/flatlaf-natives-macos/src/main/headers/JNIUtils.h
@@ -44,3 +44,7 @@
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 );
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 f99549e5..3c991a3c 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
@@ -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_NO
+#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_resolvesAliases_NO 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_showsTagField_YES
+#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_showsTagField_YES 256L
+#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_showsTagField_NO
+#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_showsTagField_NO 512L
+#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canCreateDirectories_YES
+#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canCreateDirectories_YES 1024L
+#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canCreateDirectories_NO
+#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canCreateDirectories_NO 2048L
+#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canSelectHiddenExtension
+#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canSelectHiddenExtension 4096L
+#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_showsHiddenFiles
+#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_showsHiddenFiles 16384L
+#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_extensionHidden
+#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_extensionHidden 65536L
+#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_allowsOtherFileTypes
+#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_allowsOtherFileTypes 262144L
+#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_treatsFilePackagesAsDirectories
+#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_treatsFilePackagesAsDirectories 1048576L
/*
* Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary
* Method: setWindowRoundedBorder
@@ -53,6 +79,14 @@ 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: (ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;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, jint, jobjectArray);
+
#ifdef __cplusplus
}
#endif
diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/ApiVersion.mm b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/ApiVersion.mm
index ef0ff0f3..b9b9a9b4 100644
--- a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/ApiVersion.mm
+++ b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/ApiVersion.mm
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#include
-#include "com_formdev_flatlaf_ui_FlatNativeLibrary.h"
+#import
+#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 ------------------------------------------------------------
diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/JNIUtils.mm b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/JNIUtils.mm
index c000a9e2..a5fb10e7 100644
--- a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/JNIUtils.mm
+++ b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/JNIUtils.mm
@@ -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;
+}
diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacFileChooser.mm b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacFileChooser.mm
new file mode 100644
index 00000000..d38ac371
--- /dev/null
+++ b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacFileChooser.mm
@@ -0,0 +1,152 @@
+/*
+ * 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
+#import
+#import
+#import "JNIUtils.h"
+#import "JNFRunLoop.h"
+#import "com_formdev_flatlaf_ui_FlatNativeMacLibrary.h"
+
+/**
+ * @author Karl Tauber
+ */
+
+#define isOptionSet( option ) ((options & com_formdev_flatlaf_ui_FlatNativeMacLibrary_ ## option) != 0)
+#define isYesOrNoOptionSet( option ) isOptionSet( option ## _YES ) || isOptionSet( option ## _NO )
+
+// declare internal methods
+NSWindow* getNSWindow( JNIEnv* env, jclass cls, jobject window );
+
+//---- JNI methods ------------------------------------------------------------
+
+extern "C"
+JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_showFileChooser
+ ( JNIEnv* env, jclass cls, jboolean open,
+ jstring title, jstring prompt, jstring message, jstring nameFieldLabel,
+ jstring nameFieldStringValue, jstring directoryURL,
+ jint options, jobjectArray allowedFileTypes )
+{
+ JNI_COCOA_ENTER()
+
+ // convert Java strings to NSString (on Java thread)
+ NSString* nsTitle = JavaToNSString( env, title );
+ NSString* nsPrompt = JavaToNSString( env, prompt );
+ NSString* nsMessage = JavaToNSString( env, message );
+ NSString* nsNameFieldLabel = JavaToNSString( env, nameFieldLabel );
+ NSString* nsNameFieldStringValue = JavaToNSString( env, nameFieldStringValue );
+ NSString* nsDirectoryURL = JavaToNSString( env, directoryURL );
+
+ NSArray* nsAllowedFileTypes = NULL;
+ jsize len = env->GetArrayLength( allowedFileTypes );
+ if( len > 0 ) {
+ NSMutableArray* nsArray = [NSMutableArray arrayWithCapacity:len];
+ for( int i = 0; i < len; i++ ) {
+ jstring str = (jstring) env->GetObjectArrayElement( allowedFileTypes, i );
+ NSString* nsStr = JavaToNSString( env, str );
+ nsArray[i] = nsStr;
+ }
+ nsAllowedFileTypes = nsArray;
+ }
+
+ NSArray* urls = NULL;
+ NSArray** purls = &urls;
+ NSURL* url = NULL;
+ NSURL** purl = &url;
+
+ // show file dialog on macOS thread
+ [FlatJNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){
+ NSSavePanel* dialog = open ? [NSOpenPanel openPanel] : [NSSavePanel savePanel];
+
+ 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];
+
+ 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( isOptionSet( FC_resolvesAliases_NO ) )
+ openDialog.resolvesAliases = NO;
+ if( isOptionSet( FC_allowsMultipleSelection ) )
+ openDialog.allowsMultipleSelection = YES;
+ }
+
+ if( isYesOrNoOptionSet( FC_showsTagField ) )
+ dialog.showsTagField = isOptionSet( FC_showsTagField_YES );
+ if( isYesOrNoOptionSet( FC_canCreateDirectories ) )
+ dialog.canCreateDirectories = isOptionSet( FC_canCreateDirectories_YES );
+ if( isOptionSet( FC_canSelectHiddenExtension ) )
+ dialog.canSelectHiddenExtension = YES;
+ if( isOptionSet( FC_showsHiddenFiles) )
+ dialog.showsHiddenFiles = YES;
+ if( isOptionSet( FC_extensionHidden ) )
+ dialog.extensionHidden = YES;
+ if( isOptionSet( FC_allowsOtherFileTypes ) )
+ dialog.allowsOtherFileTypes = YES;
+ if( isOptionSet( FC_treatsFilePackagesAsDirectories ) )
+ dialog.treatsFilePackagesAsDirectories = YES;
+
+ // 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
+ if( nsAllowedFileTypes != NULL )
+ dialog.allowedFileTypes = nsAllowedFileTypes;
+
+ if( [dialog runModal] != NSModalResponseOK )
+ return;
+
+ if( open )
+ *purls = ((NSOpenPanel*)dialog).URLs;
+ else
+ *purl = dialog.URL;
+ }];
+
+ if( url != NULL )
+ urls = @[url];
+
+ if( urls == NULL )
+ return NULL;
+
+ // convert URLs to Java string array
+ jsize count = urls.count;
+ jclass stringClass = env->FindClass( "java/lang/String" );
+ jobjectArray result = env->NewObjectArray( count, stringClass, NULL );
+ for( int i = 0; i < count; i++ ) {
+ jstring filename = NormalizedPathJavaFromNSString( env, [urls[i] path] );
+ env->SetObjectArrayElement( result, i, filename );
+ env->DeleteLocalRef( filename );
+ }
+
+ return result;
+
+ JNI_COCOA_EXIT()
+}
+
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMacOSFileChooserTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMacOSFileChooserTest.java
new file mode 100644
index 00000000..9f15a9da
--- /dev/null
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMacOSFileChooserTest.java
@@ -0,0 +1,446 @@
+/*
+ * 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.
+ */
+
+package com.formdev.flatlaf.testing;
+
+import static com.formdev.flatlaf.ui.FlatNativeMacLibrary.*;
+import java.awt.EventQueue;
+import java.awt.SecondaryLoop;
+import java.awt.Toolkit;
+import java.awt.Window;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowFocusListener;
+import java.awt.event.WindowListener;
+import java.awt.event.WindowStateListener;
+import java.util.Arrays;
+import javax.swing.*;
+import com.formdev.flatlaf.extras.components.*;
+import com.formdev.flatlaf.extras.components.FlatTriStateCheckBox.State;
+import com.formdev.flatlaf.ui.FlatNativeMacLibrary;
+import com.formdev.flatlaf.util.SystemInfo;
+import net.miginfocom.swing.*;
+
+/**
+ * @author Karl Tauber
+ */
+public class FlatMacOSFileChooserTest
+ extends FlatTestPanel
+{
+ public static void main( String[] args ) {
+ // macOS (see https://www.formdev.com/flatlaf/macos/)
+ if( SystemInfo.isMacOS ) {
+ // appearance of window title bars
+ // possible values:
+ // - "system": use current macOS appearance (light or dark)
+ // - "NSAppearanceNameAqua": use light appearance
+ // - "NSAppearanceNameDarkAqua": use dark appearance
+ // (needs to be set on main thread; setting it on AWT thread does not work)
+ System.setProperty( "apple.awt.application.appearance", "system" );
+ }
+
+ SwingUtilities.invokeLater( () -> {
+ FlatTestFrame frame = FlatTestFrame.create( args, "FlatMacOSFileChooserTest" );
+ addListeners( frame );
+ frame.showFrame( FlatMacOSFileChooserTest::new );
+ } );
+ }
+
+ FlatMacOSFileChooserTest() {
+ initComponents();
+ }
+
+ private void open() {
+ openOrSave( true, false );
+ }
+
+ private void save() {
+ openOrSave( false, false );
+ }
+
+ private void openDirect() {
+ openOrSave( true, true );
+ }
+
+ private void saveDirect() {
+ openOrSave( false, true );
+ }
+
+ private void openOrSave( boolean open, boolean direct ) {
+ String title = n( titleField.getText() );
+ String prompt = n( promptField.getText() );
+ String message = n( messageField.getText() );
+ String nameFieldLabel = n( nameFieldLabelField.getText() );
+ String nameFieldStringValue = n( nameFieldStringValueField.getText() );
+ String directoryURL = n( directoryURLField.getText() );
+ int options = 0;
+
+ // NSOpenPanel
+ if( canChooseFilesCheckBox.isSelected() )
+ options |= FC_canChooseFiles;
+ if( canChooseDirectoriesCheckBox.isSelected() )
+ options |= FC_canChooseDirectories;
+ if( !resolvesAliasesCheckBox.isSelected() )
+ options |= FC_resolvesAliases_NO;
+ if( allowsMultipleSelectionCheckBox.isSelected() )
+ options |= FC_allowsMultipleSelection;
+
+ // NSSavePanel
+ if( showsTagFieldCheckBox.getState() == State.SELECTED )
+ options |= FC_showsTagField_YES;
+ else if( showsTagFieldCheckBox.getState() == State.UNSELECTED )
+ options |= FC_showsTagField_NO;
+ if( canCreateDirectoriesCheckBox.getState() == State.SELECTED )
+ options |= FC_canCreateDirectories_YES;
+ else if( canCreateDirectoriesCheckBox.getState() == State.UNSELECTED )
+ options |= FC_canCreateDirectories_NO;
+ if( canSelectHiddenExtensionCheckBox.isSelected() )
+ options |= FC_canSelectHiddenExtension;
+ if( showsHiddenFilesCheckBox.isSelected() )
+ options |= FC_showsHiddenFiles;
+ if( extensionHiddenCheckBox.isSelected() )
+ options |= FC_extensionHidden;
+ if( allowsOtherFileTypesCheckBox.isSelected() )
+ options |= FC_allowsOtherFileTypes;
+ if( treatsFilePackagesAsDirectoriesCheckBox.isSelected() )
+ options |= FC_treatsFilePackagesAsDirectories;
+
+ String allowedFileTypesStr = n( allowedFileTypesField.getText() );
+ String[] allowedFileTypes = {};
+ if( allowedFileTypesStr != null )
+ allowedFileTypes = allowedFileTypesStr.trim().split( "[ ,]+" );
+
+ if( direct ) {
+ String[] files = FlatNativeMacLibrary.showFileChooser( open, title, prompt, message,
+ nameFieldLabel, nameFieldStringValue, directoryURL, options, allowedFileTypes );
+
+ filesField.setText( (files != null) ? Arrays.toString( files ).replace( ',', '\n' ) : "null" );
+ } else {
+ SecondaryLoop secondaryLoop = Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop();
+
+ int options2 = options;
+ String[] allowedFileTypes2 = allowedFileTypes;
+ new Thread( () -> {
+ String[] files = FlatNativeMacLibrary.showFileChooser( open, title, prompt, message,
+ nameFieldLabel, nameFieldStringValue, directoryURL, options2, allowedFileTypes2 );
+
+ System.out.println( " secondaryLoop.exit() returned " + secondaryLoop.exit() );
+
+ EventQueue.invokeLater( () -> {
+ filesField.setText( (files != null) ? Arrays.toString( files ).replace( ',', '\n' ) : "null" );
+ } );
+ } ).start();
+
+ System.out.println( "---- enter secondary loop ----" );
+ System.out.println( "---- secondary loop exited (secondaryLoop.enter() returned " + secondaryLoop.enter() + ") ----" );
+ }
+ }
+
+ private static String n( String s ) {
+ return !s.isEmpty() ? s : null;
+ }
+
+ private static void addListeners( Window w ) {
+ w.addWindowListener( new WindowListener() {
+ @Override
+ public void windowOpened( WindowEvent e ) {
+ System.out.println( e );
+ }
+
+ @Override
+ public void windowIconified( WindowEvent e ) {
+ System.out.println( e );
+ }
+
+ @Override
+ public void windowDeiconified( WindowEvent e ) {
+ System.out.println( e );
+ }
+
+ @Override
+ public void windowDeactivated( WindowEvent e ) {
+ System.out.println( e );
+ }
+
+ @Override
+ public void windowClosing( WindowEvent e ) {
+ System.out.println( e );
+ }
+
+ @Override
+ public void windowClosed( WindowEvent e ) {
+ System.out.println( e );
+ }
+
+ @Override
+ public void windowActivated( WindowEvent e ) {
+ System.out.println( e );
+ }
+ } );
+ w.addWindowStateListener( new WindowStateListener() {
+ @Override
+ public void windowStateChanged( WindowEvent e ) {
+ System.out.println( e );
+ }
+ } );
+ w.addWindowFocusListener( new WindowFocusListener() {
+ @Override
+ public void windowLostFocus( WindowEvent e ) {
+ System.out.println( e );
+ }
+
+ @Override
+ public void windowGainedFocus( WindowEvent e ) {
+ System.out.println( e );
+ }
+ } );
+ }
+
+ private void initComponents() {
+ // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
+ titleLabel = new JLabel();
+ titleField = new JTextField();
+ panel1 = new JPanel();
+ options1Label = new JLabel();
+ canChooseFilesCheckBox = new JCheckBox();
+ canChooseDirectoriesCheckBox = new JCheckBox();
+ resolvesAliasesCheckBox = new JCheckBox();
+ allowsMultipleSelectionCheckBox = new JCheckBox();
+ options2Label = new JLabel();
+ showsTagFieldCheckBox = new FlatTriStateCheckBox();
+ canCreateDirectoriesCheckBox = new FlatTriStateCheckBox();
+ canSelectHiddenExtensionCheckBox = new JCheckBox();
+ showsHiddenFilesCheckBox = new JCheckBox();
+ extensionHiddenCheckBox = new JCheckBox();
+ allowsOtherFileTypesCheckBox = new JCheckBox();
+ treatsFilePackagesAsDirectoriesCheckBox = new JCheckBox();
+ promptLabel = new JLabel();
+ promptField = new JTextField();
+ messageLabel = new JLabel();
+ messageField = new JTextField();
+ nameFieldLabelLabel = new JLabel();
+ nameFieldLabelField = new JTextField();
+ nameFieldStringValueLabel = new JLabel();
+ nameFieldStringValueField = new JTextField();
+ directoryURLLabel = new JLabel();
+ directoryURLField = new JTextField();
+ allowedFileTypesLabel = new JLabel();
+ allowedFileTypesField = new JTextField();
+ openButton = new JButton();
+ saveButton = new JButton();
+ openDirectButton = new JButton();
+ saveDirectButton = new JButton();
+ filesScrollPane = new JScrollPane();
+ filesField = new JTextArea();
+
+ //======== this ========
+ setLayout(new MigLayout(
+ "ltr,insets dialog,hidemode 3",
+ // columns
+ "[left]" +
+ "[grow,fill]" +
+ "[fill]",
+ // rows
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[grow,fill]"));
+
+ //---- titleLabel ----
+ titleLabel.setText("title");
+ add(titleLabel, "cell 0 0");
+ add(titleField, "cell 1 0");
+
+ //======== panel1 ========
+ {
+ panel1.setLayout(new MigLayout(
+ "insets 2,hidemode 3",
+ // columns
+ "[left]",
+ // rows
+ "[]" +
+ "[]0" +
+ "[]0" +
+ "[]0" +
+ "[]para" +
+ "[]" +
+ "[]0" +
+ "[]0" +
+ "[]0" +
+ "[]0" +
+ "[]0" +
+ "[]0" +
+ "[]"));
+
+ //---- options1Label ----
+ options1Label.setText("NSOpenPanel options:");
+ panel1.add(options1Label, "cell 0 0");
+
+ //---- canChooseFilesCheckBox ----
+ canChooseFilesCheckBox.setText("canChooseFiles");
+ canChooseFilesCheckBox.setSelected(true);
+ panel1.add(canChooseFilesCheckBox, "cell 0 1");
+
+ //---- canChooseDirectoriesCheckBox ----
+ canChooseDirectoriesCheckBox.setText("canChooseDirectories");
+ panel1.add(canChooseDirectoriesCheckBox, "cell 0 2");
+
+ //---- resolvesAliasesCheckBox ----
+ resolvesAliasesCheckBox.setText("resolvesAliases");
+ resolvesAliasesCheckBox.setSelected(true);
+ panel1.add(resolvesAliasesCheckBox, "cell 0 3");
+
+ //---- allowsMultipleSelectionCheckBox ----
+ allowsMultipleSelectionCheckBox.setText("allowsMultipleSelection");
+ panel1.add(allowsMultipleSelectionCheckBox, "cell 0 4");
+
+ //---- options2Label ----
+ options2Label.setText("NSOpenPanel and NSSavePanel options:");
+ panel1.add(options2Label, "cell 0 5");
+
+ //---- showsTagFieldCheckBox ----
+ showsTagFieldCheckBox.setText("showsTagField");
+ panel1.add(showsTagFieldCheckBox, "cell 0 6");
+
+ //---- canCreateDirectoriesCheckBox ----
+ canCreateDirectoriesCheckBox.setText("canCreateDirectories");
+ panel1.add(canCreateDirectoriesCheckBox, "cell 0 7");
+
+ //---- canSelectHiddenExtensionCheckBox ----
+ canSelectHiddenExtensionCheckBox.setText("canSelectHiddenExtension");
+ panel1.add(canSelectHiddenExtensionCheckBox, "cell 0 8");
+
+ //---- showsHiddenFilesCheckBox ----
+ showsHiddenFilesCheckBox.setText("showsHiddenFiles");
+ panel1.add(showsHiddenFilesCheckBox, "cell 0 9");
+
+ //---- extensionHiddenCheckBox ----
+ extensionHiddenCheckBox.setText("extensionHidden");
+ panel1.add(extensionHiddenCheckBox, "cell 0 10");
+
+ //---- allowsOtherFileTypesCheckBox ----
+ allowsOtherFileTypesCheckBox.setText("allowsOtherFileTypes");
+ panel1.add(allowsOtherFileTypesCheckBox, "cell 0 11");
+
+ //---- treatsFilePackagesAsDirectoriesCheckBox ----
+ treatsFilePackagesAsDirectoriesCheckBox.setText("treatsFilePackagesAsDirectories");
+ panel1.add(treatsFilePackagesAsDirectoriesCheckBox, "cell 0 12");
+ }
+ add(panel1, "cell 2 0 1 8,aligny top,growy 0");
+
+ //---- promptLabel ----
+ promptLabel.setText("prompt");
+ add(promptLabel, "cell 0 1");
+ add(promptField, "cell 1 1");
+
+ //---- messageLabel ----
+ messageLabel.setText("message");
+ add(messageLabel, "cell 0 2");
+ add(messageField, "cell 1 2");
+
+ //---- nameFieldLabelLabel ----
+ nameFieldLabelLabel.setText("nameFieldLabel");
+ add(nameFieldLabelLabel, "cell 0 3");
+ add(nameFieldLabelField, "cell 1 3");
+
+ //---- nameFieldStringValueLabel ----
+ nameFieldStringValueLabel.setText("nameFieldStringValue");
+ add(nameFieldStringValueLabel, "cell 0 4");
+ add(nameFieldStringValueField, "cell 1 4");
+
+ //---- directoryURLLabel ----
+ directoryURLLabel.setText("directoryURL");
+ add(directoryURLLabel, "cell 0 5");
+ add(directoryURLField, "cell 1 5");
+
+ //---- allowedFileTypesLabel ----
+ allowedFileTypesLabel.setText("allowedFileTypes");
+ add(allowedFileTypesLabel, "cell 0 6");
+ add(allowedFileTypesField, "cell 1 6");
+
+ //---- openButton ----
+ openButton.setText("Open...");
+ openButton.addActionListener(e -> open());
+ add(openButton, "cell 0 8 3 1");
+
+ //---- saveButton ----
+ saveButton.setText("Save...");
+ saveButton.addActionListener(e -> save());
+ add(saveButton, "cell 0 8 3 1");
+
+ //---- openDirectButton ----
+ openDirectButton.setText("Open (no-thread)...");
+ openDirectButton.addActionListener(e -> openDirect());
+ add(openDirectButton, "cell 0 8 3 1");
+
+ //---- saveDirectButton ----
+ saveDirectButton.setText("Save (no-thread)...");
+ saveDirectButton.addActionListener(e -> saveDirect());
+ add(saveDirectButton, "cell 0 8 3 1");
+
+ //======== filesScrollPane ========
+ {
+
+ //---- filesField ----
+ filesField.setRows(8);
+ filesScrollPane.setViewportView(filesField);
+ }
+ add(filesScrollPane, "cell 0 9 3 1,growx");
+ // JFormDesigner - End of component initialization //GEN-END:initComponents
+ }
+
+ // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
+ private JLabel titleLabel;
+ private JTextField titleField;
+ private JPanel panel1;
+ private JLabel options1Label;
+ private JCheckBox canChooseFilesCheckBox;
+ private JCheckBox canChooseDirectoriesCheckBox;
+ private JCheckBox resolvesAliasesCheckBox;
+ private JCheckBox allowsMultipleSelectionCheckBox;
+ private JLabel options2Label;
+ private FlatTriStateCheckBox showsTagFieldCheckBox;
+ private FlatTriStateCheckBox canCreateDirectoriesCheckBox;
+ private JCheckBox canSelectHiddenExtensionCheckBox;
+ private JCheckBox showsHiddenFilesCheckBox;
+ private JCheckBox extensionHiddenCheckBox;
+ private JCheckBox allowsOtherFileTypesCheckBox;
+ private JCheckBox treatsFilePackagesAsDirectoriesCheckBox;
+ private JLabel promptLabel;
+ private JTextField promptField;
+ private JLabel messageLabel;
+ private JTextField messageField;
+ private JLabel nameFieldLabelLabel;
+ private JTextField nameFieldLabelField;
+ private JLabel nameFieldStringValueLabel;
+ private JTextField nameFieldStringValueField;
+ private JLabel directoryURLLabel;
+ private JTextField directoryURLField;
+ private JLabel allowedFileTypesLabel;
+ private JTextField allowedFileTypesField;
+ private JButton openButton;
+ private JButton saveButton;
+ private JButton openDirectButton;
+ private JButton saveDirectButton;
+ 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/FlatMacOSFileChooserTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMacOSFileChooserTest.jfd
new file mode 100644
index 00000000..0412f9c3
--- /dev/null
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMacOSFileChooserTest.jfd
@@ -0,0 +1,220 @@
+JFDML JFormDesigner: "8.2.2.0.9999" Java: "21.0.1" encoding: "UTF-8"
+
+new FormModel {
+ contentType: "form/swing"
+ root: new FormRoot {
+ add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
+ "$layoutConstraints": "ltr,insets dialog,hidemode 3"
+ "$columnConstraints": "[left][grow,fill][fill]"
+ "$rowConstraints": "[][][][][][][][][][grow,fill]"
+ } ) {
+ name: "this"
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "titleLabel"
+ "text": "title"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 0"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "titleField"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 0"
+ } )
+ add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
+ "$layoutConstraints": "insets 2,hidemode 3"
+ "$columnConstraints": "[left]"
+ "$rowConstraints": "[][]0[]0[]0[]para[][]0[]0[]0[]0[]0[]0[]"
+ } ) {
+ name: "panel1"
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "options1Label"
+ "text": "NSOpenPanel options:"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 0"
+ } )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "canChooseFilesCheckBox"
+ "text": "canChooseFiles"
+ "selected": true
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 1"
+ } )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "canChooseDirectoriesCheckBox"
+ "text": "canChooseDirectories"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 2"
+ } )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "resolvesAliasesCheckBox"
+ "text": "resolvesAliases"
+ "selected": true
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 3"
+ } )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "allowsMultipleSelectionCheckBox"
+ "text": "allowsMultipleSelection"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 4"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "options2Label"
+ "text": "NSOpenPanel and NSSavePanel options:"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 5"
+ } )
+ add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) {
+ name: "showsTagFieldCheckBox"
+ "text": "showsTagField"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 6"
+ } )
+ add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) {
+ name: "canCreateDirectoriesCheckBox"
+ "text": "canCreateDirectories"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 7"
+ } )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "canSelectHiddenExtensionCheckBox"
+ "text": "canSelectHiddenExtension"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 8"
+ } )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "showsHiddenFilesCheckBox"
+ "text": "showsHiddenFiles"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 9"
+ } )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "extensionHiddenCheckBox"
+ "text": "extensionHidden"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 10"
+ } )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "allowsOtherFileTypesCheckBox"
+ "text": "allowsOtherFileTypes"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 11"
+ } )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "treatsFilePackagesAsDirectoriesCheckBox"
+ "text": "treatsFilePackagesAsDirectories"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 12"
+ } )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 2 0 1 8,aligny top,growy 0"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "promptLabel"
+ "text": "prompt"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 1"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "promptField"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 1"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "messageLabel"
+ "text": "message"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 2"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "messageField"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 2"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "nameFieldLabelLabel"
+ "text": "nameFieldLabel"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 3"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "nameFieldLabelField"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 3"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "nameFieldStringValueLabel"
+ "text": "nameFieldStringValue"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 4"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "nameFieldStringValueField"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 4"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "directoryURLLabel"
+ "text": "directoryURL"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 5"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "directoryURLField"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 5"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "allowedFileTypesLabel"
+ "text": "allowedFileTypes"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 6"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "allowedFileTypesField"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 6"
+ } )
+ add( new FormComponent( "javax.swing.JButton" ) {
+ name: "openButton"
+ "text": "Open..."
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "open", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 8 3 1"
+ } )
+ add( new FormComponent( "javax.swing.JButton" ) {
+ name: "saveButton"
+ "text": "Save..."
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "save", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 8 3 1"
+ } )
+ add( new FormComponent( "javax.swing.JButton" ) {
+ name: "openDirectButton"
+ "text": "Open (no-thread)..."
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "openDirect", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 8 3 1"
+ } )
+ add( new FormComponent( "javax.swing.JButton" ) {
+ name: "saveDirectButton"
+ "text": "Save (no-thread)..."
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "saveDirect", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 8 3 1"
+ } )
+ add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
+ name: "filesScrollPane"
+ add( new FormComponent( "javax.swing.JTextArea" ) {
+ name: "filesField"
+ "rows": 8
+ } )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 9 3 1,growx"
+ } )
+ }, new FormLayoutConstraints( null ) {
+ "location": new java.awt.Point( 0, 0 )
+ "size": new java.awt.Dimension( 535, 465 )
+ } )
+ }
+}