System File Chooser: implemented native bindings for NSOpenPanel and NSSavePanel on macOS

This commit is contained in:
Karl Tauber
2024-12-30 12:46:28 +01:00
parent 80ba75fdeb
commit 516bd80702
8 changed files with 941 additions and 4 deletions

View File

@@ -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 );

View File

@@ -13,6 +13,32 @@ extern "C" {
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTONS_SPACING_MEDIUM 1L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTONS_SPACING_LARGE
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTONS_SPACING_LARGE 2L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canChooseFiles
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canChooseFiles 1L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canChooseDirectories
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canChooseDirectories 2L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_resolvesAliases_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

View File

@@ -14,8 +14,8 @@
* limitations under the License.
*/
#include <jni.h>
#include "com_formdev_flatlaf_ui_FlatNativeLibrary.h"
#import <jni.h>
#import "com_formdev_flatlaf_ui_FlatNativeLibrary.h"
/**
* @author Karl Tauber
@@ -24,7 +24,7 @@
// increase this version if changing API or functionality of native library
// also update version in Java class com.formdev.flatlaf.ui.FlatNativeMacLibrary
#define API_VERSION_MACOS 2001
#define API_VERSION_MACOS 2002
//---- JNI methods ------------------------------------------------------------

View File

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

View File

@@ -0,0 +1,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 <Cocoa/Cocoa.h>
#import <objc/runtime.h>
#import <jni.h>
#import "JNIUtils.h"
#import "JNFRunLoop.h"
#import "com_formdev_flatlaf_ui_FlatNativeMacLibrary.h"
/**
* @author Karl Tauber
*/
#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()
}