2017-09-30 12:21:34 +00:00
|
|
|
/*
|
2022-04-20 16:09:28 +00:00
|
|
|
Native File Dialog Extended
|
|
|
|
Repository: https://github.com/btzy/nativefiledialog-extended
|
|
|
|
License: Zlib
|
|
|
|
Authors: Bernard Teo, Michael Labbe
|
2017-09-30 12:21:34 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <AppKit/AppKit.h>
|
|
|
|
#include "nfd.h"
|
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
static const char* g_errorstr = NULL;
|
2017-09-30 12:21:34 +00:00
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
static void NFDi_SetError(const char* msg) {
|
|
|
|
g_errorstr = msg;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void* NFDi_Malloc(size_t bytes) {
|
|
|
|
void* ptr = malloc(bytes);
|
|
|
|
if (!ptr) NFDi_SetError("NFDi_Malloc failed.");
|
|
|
|
|
|
|
|
return ptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void NFDi_Free(void* ptr) {
|
|
|
|
assert(ptr);
|
|
|
|
free(ptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
static NSArray* BuildAllowedFileTypes(const nfdnfilteritem_t* filterList,
|
|
|
|
nfdfiltersize_t filterCount) {
|
|
|
|
// Commas and semicolons are the same thing on this platform
|
2017-09-30 12:21:34 +00:00
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
NSMutableArray* buildFilterList = [[NSMutableArray alloc] init];
|
|
|
|
|
|
|
|
for (nfdfiltersize_t filterIndex = 0; filterIndex != filterCount; ++filterIndex) {
|
|
|
|
// this is the spec to parse (we don't use the friendly name on OS X)
|
|
|
|
const nfdnchar_t* filterSpec = filterList[filterIndex].spec;
|
|
|
|
|
|
|
|
const nfdnchar_t* p_currentFilterBegin = filterSpec;
|
|
|
|
for (const nfdnchar_t* p_filterSpec = filterSpec; *p_filterSpec; ++p_filterSpec) {
|
|
|
|
if (*p_filterSpec == ',') {
|
|
|
|
// add the extension to the array
|
|
|
|
NSString* filterStr = [[[NSString alloc]
|
|
|
|
initWithBytes:(const void*)p_currentFilterBegin
|
|
|
|
length:(sizeof(nfdnchar_t) * (p_filterSpec - p_currentFilterBegin))
|
|
|
|
encoding:NSUTF8StringEncoding] autorelease];
|
|
|
|
[buildFilterList addObject:filterStr];
|
|
|
|
p_currentFilterBegin = p_filterSpec + 1;
|
|
|
|
}
|
2017-09-30 12:21:34 +00:00
|
|
|
}
|
2022-04-20 16:09:28 +00:00
|
|
|
// add the extension to the array
|
|
|
|
NSString* filterStr = [NSString stringWithUTF8String:p_currentFilterBegin];
|
|
|
|
[buildFilterList addObject:filterStr];
|
2017-09-30 12:21:34 +00:00
|
|
|
}
|
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
NSArray* returnArray = [NSArray arrayWithArray:buildFilterList];
|
2017-09-30 12:21:34 +00:00
|
|
|
|
|
|
|
[buildFilterList release];
|
2022-04-20 16:09:28 +00:00
|
|
|
|
|
|
|
assert([returnArray count] != 0);
|
|
|
|
|
2017-09-30 12:21:34 +00:00
|
|
|
return returnArray;
|
|
|
|
}
|
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
static void AddFilterListToDialog(NSSavePanel* dialog,
|
|
|
|
const nfdnfilteritem_t* filterList,
|
|
|
|
nfdfiltersize_t filterCount) {
|
|
|
|
// note: NSOpenPanel inherits from NSSavePanel.
|
2017-09-30 12:21:34 +00:00
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
if (!filterCount) return;
|
|
|
|
|
|
|
|
assert(filterList);
|
|
|
|
|
|
|
|
// make NSArray of file types
|
|
|
|
NSArray* allowedFileTypes = BuildAllowedFileTypes(filterList, filterCount);
|
|
|
|
|
|
|
|
// set it on the dialog
|
|
|
|
[dialog setAllowedFileTypes:allowedFileTypes];
|
2017-09-30 12:21:34 +00:00
|
|
|
}
|
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
static void SetDefaultPath(NSSavePanel* dialog, const nfdnchar_t* defaultPath) {
|
|
|
|
if (!defaultPath || !*defaultPath) return;
|
2017-09-30 12:21:34 +00:00
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
NSString* defaultPathString = [NSString stringWithUTF8String:defaultPath];
|
|
|
|
NSURL* url = [NSURL fileURLWithPath:defaultPathString isDirectory:YES];
|
|
|
|
[dialog setDirectoryURL:url];
|
2017-09-30 12:21:34 +00:00
|
|
|
}
|
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
static void SetDefaultName(NSSavePanel* dialog, const nfdnchar_t* defaultName) {
|
|
|
|
if (!defaultName || !*defaultName) return;
|
2017-09-30 12:21:34 +00:00
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
NSString* defaultNameString = [NSString stringWithUTF8String:defaultName];
|
|
|
|
[dialog setNameFieldStringValue:defaultNameString];
|
|
|
|
}
|
2017-09-30 12:21:34 +00:00
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
static nfdresult_t CopyUtf8String(const char* utf8Str, nfdnchar_t** out) {
|
|
|
|
// byte count, not char count
|
|
|
|
size_t len = strlen(utf8Str);
|
2017-09-30 12:21:34 +00:00
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
// Too bad we have to use additional memory for all the result paths,
|
|
|
|
// because we cannot reconstitute an NSString from a char* to release it properly.
|
|
|
|
*out = (nfdnchar_t*)NFDi_Malloc(len + 1);
|
|
|
|
if (*out) {
|
|
|
|
strcpy(*out, utf8Str);
|
|
|
|
return NFD_OKAY;
|
2017-09-30 12:21:34 +00:00
|
|
|
}
|
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
return NFD_ERROR;
|
|
|
|
}
|
2017-09-30 12:21:34 +00:00
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
/* public */
|
|
|
|
|
|
|
|
const char* NFD_GetError(void) {
|
|
|
|
return g_errorstr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void NFD_FreePathN(nfdnchar_t* filePath) {
|
|
|
|
NFDi_Free((void*)filePath);
|
|
|
|
}
|
|
|
|
|
|
|
|
static NSApplicationActivationPolicy old_app_policy;
|
2017-09-30 12:21:34 +00:00
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
nfdresult_t NFD_Init(void) {
|
|
|
|
NSApplication* app = [NSApplication sharedApplication];
|
|
|
|
old_app_policy = [app activationPolicy];
|
|
|
|
if (old_app_policy == NSApplicationActivationPolicyProhibited) {
|
|
|
|
if (![app setActivationPolicy:NSApplicationActivationPolicyAccessory]) {
|
|
|
|
NFDi_SetError("Failed to set activation policy.");
|
|
|
|
return NFD_ERROR;
|
|
|
|
}
|
|
|
|
}
|
2017-09-30 12:21:34 +00:00
|
|
|
return NFD_OKAY;
|
|
|
|
}
|
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
/* call this to de-initialize NFD, if NFD_Init returned NFD_OKAY */
|
|
|
|
void NFD_Quit(void) {
|
|
|
|
[[NSApplication sharedApplication] setActivationPolicy:old_app_policy];
|
|
|
|
}
|
|
|
|
|
|
|
|
nfdresult_t NFD_OpenDialogN(nfdnchar_t** outPath,
|
|
|
|
const nfdnfilteritem_t* filterList,
|
|
|
|
nfdfiltersize_t filterCount,
|
|
|
|
const nfdnchar_t* defaultPath) {
|
|
|
|
nfdresult_t result = NFD_CANCEL;
|
|
|
|
@autoreleasepool {
|
|
|
|
NSWindow* keyWindow = [[NSApplication sharedApplication] keyWindow];
|
|
|
|
|
|
|
|
NSOpenPanel* dialog = [NSOpenPanel openPanel];
|
|
|
|
[dialog setAllowsMultipleSelection:NO];
|
|
|
|
|
|
|
|
// Build the filter list
|
|
|
|
AddFilterListToDialog(dialog, filterList, filterCount);
|
2017-09-30 12:21:34 +00:00
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
// Set the starting directory
|
|
|
|
SetDefaultPath(dialog, defaultPath);
|
2017-09-30 12:21:34 +00:00
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
if ([dialog runModal] == NSModalResponseOK) {
|
|
|
|
const NSURL* url = [dialog URL];
|
|
|
|
const char* utf8Path = [[url path] UTF8String];
|
|
|
|
result = CopyUtf8String(utf8Path, outPath);
|
|
|
|
}
|
2017-09-30 12:21:34 +00:00
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
// return focus to the key window (i.e. main window)
|
|
|
|
[keyWindow makeKeyAndOrderFront:nil];
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2017-09-30 12:21:34 +00:00
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths,
|
|
|
|
const nfdnfilteritem_t* filterList,
|
|
|
|
nfdfiltersize_t filterCount,
|
|
|
|
const nfdnchar_t* defaultPath) {
|
|
|
|
nfdresult_t result = NFD_CANCEL;
|
|
|
|
@autoreleasepool {
|
|
|
|
NSWindow* keyWindow = [[NSApplication sharedApplication] keyWindow];
|
2017-09-30 12:21:34 +00:00
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
NSOpenPanel* dialog = [NSOpenPanel openPanel];
|
|
|
|
[dialog setAllowsMultipleSelection:YES];
|
2017-09-30 12:21:34 +00:00
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
// Build the filter list
|
|
|
|
AddFilterListToDialog(dialog, filterList, filterCount);
|
2017-09-30 12:21:34 +00:00
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
// Set the starting directory
|
|
|
|
SetDefaultPath(dialog, defaultPath);
|
2017-09-30 12:21:34 +00:00
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
if ([dialog runModal] == NSModalResponseOK) {
|
|
|
|
const NSArray* urls = [dialog URLs];
|
|
|
|
|
|
|
|
if ([urls count] > 0) {
|
|
|
|
// have at least one URL, we return this NSArray
|
|
|
|
[urls retain];
|
|
|
|
*outPaths = (const nfdpathset_t*)urls;
|
|
|
|
result = NFD_OKAY;
|
|
|
|
}
|
2017-09-30 12:21:34 +00:00
|
|
|
}
|
2022-04-20 16:09:28 +00:00
|
|
|
|
|
|
|
// return focus to the key window (i.e. main window)
|
|
|
|
[keyWindow makeKeyAndOrderFront:nil];
|
2017-09-30 12:21:34 +00:00
|
|
|
}
|
2022-04-20 16:09:28 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath,
|
|
|
|
const nfdnfilteritem_t* filterList,
|
|
|
|
nfdfiltersize_t filterCount,
|
|
|
|
const nfdnchar_t* defaultPath,
|
|
|
|
const nfdnchar_t* defaultName) {
|
|
|
|
nfdresult_t result = NFD_CANCEL;
|
|
|
|
@autoreleasepool {
|
|
|
|
NSWindow* keyWindow = [[NSApplication sharedApplication] keyWindow];
|
|
|
|
|
|
|
|
NSSavePanel* dialog = [NSSavePanel savePanel];
|
|
|
|
[dialog setExtensionHidden:NO];
|
|
|
|
// allow other file types, to give the user an escape hatch since you can't select "*.*" on
|
|
|
|
// Mac
|
|
|
|
[dialog setAllowsOtherFileTypes:TRUE];
|
|
|
|
|
|
|
|
// Build the filter list
|
|
|
|
AddFilterListToDialog(dialog, filterList, filterCount);
|
|
|
|
|
|
|
|
// Set the starting directory
|
|
|
|
SetDefaultPath(dialog, defaultPath);
|
|
|
|
|
|
|
|
// Set the default file name
|
|
|
|
SetDefaultName(dialog, defaultName);
|
|
|
|
|
|
|
|
if ([dialog runModal] == NSModalResponseOK) {
|
|
|
|
const NSURL* url = [dialog URL];
|
|
|
|
const char* utf8Path = [[url path] UTF8String];
|
|
|
|
result = CopyUtf8String(utf8Path, outPath);
|
|
|
|
}
|
2017-09-30 12:21:34 +00:00
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
// return focus to the key window (i.e. main window)
|
|
|
|
[keyWindow makeKeyAndOrderFront:nil];
|
|
|
|
}
|
|
|
|
return result;
|
2017-09-30 12:21:34 +00:00
|
|
|
}
|
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath) {
|
|
|
|
nfdresult_t result = NFD_CANCEL;
|
|
|
|
@autoreleasepool {
|
|
|
|
NSWindow* keyWindow = [[NSApplication sharedApplication] keyWindow];
|
2017-09-30 12:21:34 +00:00
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
NSOpenPanel* dialog = [NSOpenPanel openPanel];
|
|
|
|
[dialog setAllowsMultipleSelection:NO];
|
|
|
|
[dialog setCanChooseDirectories:YES];
|
|
|
|
[dialog setCanCreateDirectories:YES];
|
|
|
|
[dialog setCanChooseFiles:NO];
|
2017-09-30 12:21:34 +00:00
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
// Set the starting directory
|
|
|
|
SetDefaultPath(dialog, defaultPath);
|
|
|
|
|
|
|
|
if ([dialog runModal] == NSModalResponseOK) {
|
|
|
|
const NSURL* url = [dialog URL];
|
|
|
|
const char* utf8Path = [[url path] UTF8String];
|
|
|
|
result = CopyUtf8String(utf8Path, outPath);
|
2017-09-30 12:21:34 +00:00
|
|
|
}
|
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
// return focus to the key window (i.e. main window)
|
|
|
|
[keyWindow makeKeyAndOrderFront:nil];
|
2017-09-30 12:21:34 +00:00
|
|
|
}
|
2022-04-20 16:09:28 +00:00
|
|
|
return result;
|
|
|
|
}
|
2017-09-30 12:21:34 +00:00
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, nfdpathsetsize_t* count) {
|
|
|
|
const NSArray* urls = (const NSArray*)pathSet;
|
|
|
|
*count = [urls count];
|
|
|
|
return NFD_OKAY;
|
2017-09-30 12:21:34 +00:00
|
|
|
}
|
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
nfdresult_t NFD_PathSet_GetPathN(const nfdpathset_t* pathSet,
|
|
|
|
nfdpathsetsize_t index,
|
|
|
|
nfdnchar_t** outPath) {
|
|
|
|
const NSArray* urls = (const NSArray*)pathSet;
|
2017-09-30 12:21:34 +00:00
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
@autoreleasepool {
|
|
|
|
// autoreleasepool needed because UTF8String method might use the pool
|
|
|
|
const NSURL* url = [urls objectAtIndex:index];
|
|
|
|
const char* utf8Path = [[url path] UTF8String];
|
|
|
|
return CopyUtf8String(utf8Path, outPath);
|
2017-09-30 12:21:34 +00:00
|
|
|
}
|
2022-04-20 16:09:28 +00:00
|
|
|
}
|
2017-09-30 12:21:34 +00:00
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
void NFD_PathSet_Free(const nfdpathset_t* pathSet) {
|
|
|
|
const NSArray* urls = (const NSArray*)pathSet;
|
|
|
|
[urls release];
|
2017-09-30 12:21:34 +00:00
|
|
|
}
|
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
nfdresult_t NFD_PathSet_GetEnum(const nfdpathset_t* pathSet, nfdpathsetenum_t* outEnumerator) {
|
|
|
|
const NSArray* urls = (const NSArray*)pathSet;
|
|
|
|
|
|
|
|
@autoreleasepool {
|
|
|
|
// autoreleasepool needed because NSEnumerator uses it
|
|
|
|
NSEnumerator* enumerator = [urls objectEnumerator];
|
|
|
|
[enumerator retain];
|
|
|
|
outEnumerator->ptr = (void*)enumerator;
|
2017-09-30 12:21:34 +00:00
|
|
|
}
|
|
|
|
|
2022-04-20 16:09:28 +00:00
|
|
|
return NFD_OKAY;
|
|
|
|
}
|
|
|
|
|
|
|
|
void NFD_PathSet_FreeEnum(nfdpathsetenum_t* enumerator) {
|
|
|
|
NSEnumerator* real_enum = (NSEnumerator*)enumerator->ptr;
|
|
|
|
[real_enum release];
|
|
|
|
}
|
|
|
|
|
|
|
|
nfdresult_t NFD_PathSet_EnumNextN(nfdpathsetenum_t* enumerator, nfdnchar_t** outPath) {
|
|
|
|
NSEnumerator* real_enum = (NSEnumerator*)enumerator->ptr;
|
|
|
|
|
|
|
|
@autoreleasepool {
|
|
|
|
// autoreleasepool needed because NSURL uses it
|
|
|
|
const NSURL* url = [real_enum nextObject];
|
|
|
|
if (url) {
|
|
|
|
const char* utf8Path = [[url path] UTF8String];
|
|
|
|
return CopyUtf8String(utf8Path, outPath);
|
|
|
|
} else {
|
|
|
|
*outPath = NULL;
|
|
|
|
return NFD_OKAY;
|
|
|
|
}
|
|
|
|
}
|
2017-09-30 12:21:34 +00:00
|
|
|
}
|