mirror of
https://github.com/wolfpld/tracy.git
synced 2024-11-25 07:24:36 +00:00
Switch NFD to NFD Extended.
This commit is contained in:
parent
1a1d7bbb54
commit
70a8da90ad
21
nfd/common.h
21
nfd/common.h
@ -1,21 +0,0 @@
|
|||||||
/*
|
|
||||||
Native File Dialog
|
|
||||||
|
|
||||||
Internal, common across platforms
|
|
||||||
|
|
||||||
http://www.frogtoss.com/labs
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef _NFD_COMMON_H
|
|
||||||
#define _NFD_COMMON_H
|
|
||||||
|
|
||||||
#define NFD_MAX_STRLEN 256
|
|
||||||
#define _NFD_UNUSED(x) ((void)x)
|
|
||||||
|
|
||||||
void *NFDi_Malloc( size_t bytes );
|
|
||||||
void NFDi_Free( void *ptr );
|
|
||||||
void NFDi_SetError( const char *msg );
|
|
||||||
void NFDi_SafeStrncpy( char *dst, const char *src, size_t maxCopy );
|
|
||||||
|
|
||||||
#endif
|
|
285
nfd/nfd.h
285
nfd/nfd.h
@ -1,76 +1,281 @@
|
|||||||
/*
|
/*
|
||||||
Native File Dialog
|
Native File Dialog Extended
|
||||||
|
Repository: https://github.com/btzy/nativefiledialog-extended
|
||||||
|
License: Zlib
|
||||||
|
Authors: Bernard Teo, Michael Labbe
|
||||||
|
|
||||||
User API
|
This header contains the functions that can be called by user code.
|
||||||
|
|
||||||
http://www.frogtoss.com/labs
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#ifndef _NFD_H
|
#ifndef _NFD_H
|
||||||
#define _NFD_H
|
#define _NFD_H
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif // __cplusplus
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
/* denotes UTF-16 char */
|
||||||
|
typedef wchar_t nfdnchar_t;
|
||||||
|
#else
|
||||||
/* denotes UTF-8 char */
|
/* denotes UTF-8 char */
|
||||||
typedef char nfdchar_t;
|
typedef char nfdnchar_t;
|
||||||
|
#endif // _WIN32
|
||||||
|
|
||||||
/* opaque data structure -- see NFD_PathSet_* */
|
/* opaque data structure -- see NFD_PathSet_* */
|
||||||
|
typedef void nfdpathset_t;
|
||||||
|
#ifndef NFD_PORTAL
|
||||||
typedef struct {
|
typedef struct {
|
||||||
nfdchar_t *buf;
|
void* ptr;
|
||||||
size_t *indices; /* byte offsets into buf */
|
} nfdpathsetenum_t;
|
||||||
size_t count; /* number of indices into buf */
|
#else
|
||||||
}nfdpathset_t;
|
typedef struct {
|
||||||
|
void* d1;
|
||||||
|
void* d2;
|
||||||
|
unsigned int d3;
|
||||||
|
int d4;
|
||||||
|
int d5;
|
||||||
|
int d6;
|
||||||
|
int d7;
|
||||||
|
int d8;
|
||||||
|
int d9;
|
||||||
|
int d10;
|
||||||
|
int d11;
|
||||||
|
int p1;
|
||||||
|
void* p2;
|
||||||
|
void* p3;
|
||||||
|
} nfdpathsetenum_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef unsigned int nfdfiltersize_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
NFD_ERROR, /* programmatic error */
|
NFD_ERROR, /* programmatic error */
|
||||||
NFD_OKAY, /* user pressed okay, or successful return */
|
NFD_OKAY, /* user pressed okay, or successful return */
|
||||||
NFD_CANCEL /* user pressed cancel */
|
NFD_CANCEL /* user pressed cancel */
|
||||||
}nfdresult_t;
|
} nfdresult_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const nfdnchar_t* name;
|
||||||
|
const nfdnchar_t* spec;
|
||||||
|
} nfdnfilteritem_t;
|
||||||
|
|
||||||
/* nfd_<targetplatform>.c */
|
/* free a file path that was returned by the dialogs */
|
||||||
|
/* Note: use NFD_PathSet_FreePath to free path from pathset instead of this function */
|
||||||
|
void NFD_FreePathN(nfdnchar_t* filePath);
|
||||||
|
|
||||||
|
/* initialize NFD - call this for every thread that might use NFD, before calling any other NFD
|
||||||
|
* functions on that thread */
|
||||||
|
nfdresult_t NFD_Init(void);
|
||||||
|
|
||||||
|
/* call this to de-initialize NFD, if NFD_Init returned NFD_OKAY */
|
||||||
|
void NFD_Quit(void);
|
||||||
|
|
||||||
/* single file open dialog */
|
/* single file open dialog */
|
||||||
nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList,
|
/* It is the caller's responsibility to free `outPath` via NFD_FreePathN() if this function returns
|
||||||
const nfdchar_t *defaultPath,
|
* NFD_OKAY */
|
||||||
nfdchar_t **outPath,
|
/* If filterCount is zero, filterList is ignored (you can use NULL) */
|
||||||
void* owner );
|
/* If defaultPath is NULL, the operating system will decide */
|
||||||
|
nfdresult_t NFD_OpenDialogN(nfdnchar_t** outPath,
|
||||||
|
const nfdnfilteritem_t* filterList,
|
||||||
|
nfdfiltersize_t filterCount,
|
||||||
|
const nfdnchar_t* defaultPath);
|
||||||
|
|
||||||
/* multiple file open dialog */
|
/* multiple file open dialog */
|
||||||
nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
|
/* It is the caller's responsibility to free `outPaths` via NFD_PathSet_Free() if this function
|
||||||
const nfdchar_t *defaultPath,
|
* returns NFD_OKAY */
|
||||||
nfdpathset_t *outPaths );
|
/* If filterCount is zero, filterList is ignored (you can use NULL) */
|
||||||
|
/* If defaultPath is NULL, the operating system will decide */
|
||||||
|
nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths,
|
||||||
|
const nfdnfilteritem_t* filterList,
|
||||||
|
nfdfiltersize_t filterCount,
|
||||||
|
const nfdnchar_t* defaultPath);
|
||||||
|
|
||||||
/* save dialog */
|
/* save dialog */
|
||||||
nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
|
/* It is the caller's responsibility to free `outPath` via NFD_FreePathN() if this function returns
|
||||||
const nfdchar_t *defaultPath,
|
* NFD_OKAY */
|
||||||
nfdchar_t **outPath,
|
/* If filterCount is zero, filterList is ignored (you can use NULL) */
|
||||||
void* owner );
|
/* If defaultPath is NULL, the operating system will decide */
|
||||||
|
nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath,
|
||||||
|
const nfdnfilteritem_t* filterList,
|
||||||
|
nfdfiltersize_t filterCount,
|
||||||
|
const nfdnchar_t* defaultPath,
|
||||||
|
const nfdnchar_t* defaultName);
|
||||||
|
|
||||||
/* select folder dialog */
|
/* select folder dialog */
|
||||||
nfdresult_t NFD_PickFolder( const nfdchar_t *defaultPath,
|
/* It is the caller's responsibility to free `outPath` via NFD_FreePathN() if this function returns
|
||||||
nfdchar_t **outPath);
|
* NFD_OKAY */
|
||||||
|
/* If defaultPath is NULL, the operating system will decide */
|
||||||
|
nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath);
|
||||||
|
|
||||||
/* nfd_common.c */
|
/* Get last error -- set when nfdresult_t returns NFD_ERROR */
|
||||||
|
/* Returns the last error that was set, or NULL if there is no error. */
|
||||||
|
/* The memory is owned by NFD and should not be freed by user code. */
|
||||||
|
/* This is *always* ASCII printable characters, so it can be interpreted as UTF-8 without any
|
||||||
|
* conversion. */
|
||||||
|
const char* NFD_GetError(void);
|
||||||
|
/* clear the error */
|
||||||
|
void NFD_ClearError(void);
|
||||||
|
|
||||||
|
/* path set operations */
|
||||||
|
#ifdef _WIN32
|
||||||
|
typedef unsigned long nfdpathsetsize_t;
|
||||||
|
#elif __APPLE__
|
||||||
|
typedef unsigned long nfdpathsetsize_t;
|
||||||
|
#else
|
||||||
|
typedef unsigned int nfdpathsetsize_t;
|
||||||
|
#endif // _WIN32, __APPLE__
|
||||||
|
|
||||||
|
/* Gets the number of entries stored in pathSet */
|
||||||
|
/* note that some paths might be invalid (NFD_ERROR will be returned by NFD_PathSet_GetPath), so we
|
||||||
|
* might not actually have this number of usable paths */
|
||||||
|
nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, nfdpathsetsize_t* count);
|
||||||
|
/* Gets the UTF-8 path at offset index */
|
||||||
|
/* It is the caller's responsibility to free `outPath` via NFD_PathSet_FreePathN() if this function
|
||||||
|
* returns NFD_OKAY */
|
||||||
|
nfdresult_t NFD_PathSet_GetPathN(const nfdpathset_t* pathSet,
|
||||||
|
nfdpathsetsize_t index,
|
||||||
|
nfdnchar_t** outPath);
|
||||||
|
/* Free the path gotten by NFD_PathSet_GetPathN */
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define NFD_PathSet_FreePathN NFD_FreePathN
|
||||||
|
#elif __APPLE__
|
||||||
|
#define NFD_PathSet_FreePathN NFD_FreePathN
|
||||||
|
#else
|
||||||
|
void NFD_PathSet_FreePathN(const nfdnchar_t* filePath);
|
||||||
|
#endif // _WIN32, __APPLE__
|
||||||
|
|
||||||
|
/* Gets an enumerator of the path set. */
|
||||||
|
/* It is the caller's responsibility to free `enumerator` via NFD_PathSet_FreeEnum() if this
|
||||||
|
* function returns NFD_OKAY, and it should be freed before freeing the pathset. */
|
||||||
|
nfdresult_t NFD_PathSet_GetEnum(const nfdpathset_t* pathSet, nfdpathsetenum_t* outEnumerator);
|
||||||
|
/* Frees an enumerator of the path set. */
|
||||||
|
void NFD_PathSet_FreeEnum(nfdpathsetenum_t* enumerator);
|
||||||
|
/* Gets the next item from the path set enumerator.
|
||||||
|
* If there are no more items, then *outPaths will be set to NULL. */
|
||||||
|
/* It is the caller's responsibility to free `*outPath` via NFD_PathSet_FreePath() if this
|
||||||
|
* function returns NFD_OKAY and `*outPath` is not null */
|
||||||
|
nfdresult_t NFD_PathSet_EnumNextN(nfdpathsetenum_t* enumerator, nfdnchar_t** outPath);
|
||||||
|
|
||||||
/* get last error -- set when nfdresult_t returns NFD_ERROR */
|
|
||||||
const char *NFD_GetError( void );
|
|
||||||
/* get the number of entries stored in pathSet */
|
|
||||||
size_t NFD_PathSet_GetCount( const nfdpathset_t *pathSet );
|
|
||||||
/* Get the UTF-8 path at offset index */
|
|
||||||
nfdchar_t *NFD_PathSet_GetPath( const nfdpathset_t *pathSet, size_t index );
|
|
||||||
/* Free the pathSet */
|
/* Free the pathSet */
|
||||||
void NFD_PathSet_Free( nfdpathset_t *pathSet );
|
void NFD_PathSet_Free(const nfdpathset_t* pathSet);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
/* say that the U8 versions of functions are not just #defined to be the native versions */
|
||||||
|
#define NFD_DIFFERENT_NATIVE_FUNCTIONS
|
||||||
|
|
||||||
|
typedef char nfdu8char_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const nfdu8char_t* name;
|
||||||
|
const nfdu8char_t* spec;
|
||||||
|
} nfdu8filteritem_t;
|
||||||
|
|
||||||
|
/* UTF-8 compatibility functions */
|
||||||
|
|
||||||
|
/* free a file path that was returned */
|
||||||
|
void NFD_FreePathU8(nfdu8char_t* outPath);
|
||||||
|
|
||||||
|
/* single file open dialog */
|
||||||
|
/* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns
|
||||||
|
* NFD_OKAY */
|
||||||
|
nfdresult_t NFD_OpenDialogU8(nfdu8char_t** outPath,
|
||||||
|
const nfdu8filteritem_t* filterList,
|
||||||
|
nfdfiltersize_t count,
|
||||||
|
const nfdu8char_t* defaultPath);
|
||||||
|
|
||||||
|
/* multiple file open dialog */
|
||||||
|
/* It is the caller's responsibility to free `outPaths` via NFD_PathSet_Free() if this function
|
||||||
|
* returns NFD_OKAY */
|
||||||
|
nfdresult_t NFD_OpenDialogMultipleU8(const nfdpathset_t** outPaths,
|
||||||
|
const nfdu8filteritem_t* filterList,
|
||||||
|
nfdfiltersize_t count,
|
||||||
|
const nfdu8char_t* defaultPath);
|
||||||
|
|
||||||
|
/* save dialog */
|
||||||
|
/* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns
|
||||||
|
* NFD_OKAY */
|
||||||
|
nfdresult_t NFD_SaveDialogU8(nfdu8char_t** outPath,
|
||||||
|
const nfdu8filteritem_t* filterList,
|
||||||
|
nfdfiltersize_t count,
|
||||||
|
const nfdu8char_t* defaultPath,
|
||||||
|
const nfdu8char_t* defaultName);
|
||||||
|
|
||||||
|
/* select folder dialog */
|
||||||
|
/* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns
|
||||||
|
* NFD_OKAY */
|
||||||
|
nfdresult_t NFD_PickFolderU8(nfdu8char_t** outPath, const nfdu8char_t* defaultPath);
|
||||||
|
|
||||||
|
/* Get the UTF-8 path at offset index */
|
||||||
|
/* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns
|
||||||
|
* NFD_OKAY */
|
||||||
|
nfdresult_t NFD_PathSet_GetPathU8(const nfdpathset_t* pathSet,
|
||||||
|
nfdpathsetsize_t index,
|
||||||
|
nfdu8char_t** outPath);
|
||||||
|
|
||||||
|
/* Gets the next item from the path set enumerator.
|
||||||
|
* If there are no more items, then *outPaths will be set to NULL. */
|
||||||
|
/* It is the caller's responsibility to free `*outPath` via NFD_PathSet_FreePathU8() if this
|
||||||
|
* function returns NFD_OKAY and `*outPath` is not null */
|
||||||
|
nfdresult_t NFD_PathSet_EnumNextU8(nfdpathsetenum_t* enumerator, nfdu8char_t** outPath);
|
||||||
|
|
||||||
|
#define NFD_PathSet_FreePathU8 NFD_FreePathU8
|
||||||
|
|
||||||
|
#ifdef NFD_NATIVE
|
||||||
|
typedef nfdnchar_t nfdchar_t;
|
||||||
|
typedef nfdnfilteritem_t nfdfilteritem_t;
|
||||||
|
#define NFD_FreePath NFD_FreePathN
|
||||||
|
#define NFD_OpenDialog NFD_OpenDialogN
|
||||||
|
#define NFD_OpenDialogMultiple NFD_OpenDialogMultipleN
|
||||||
|
#define NFD_SaveDialog NFD_SaveDialogN
|
||||||
|
#define NFD_PickFolder NFD_PickFolderN
|
||||||
|
#define NFD_PathSet_GetPath NFD_PathSet_GetPathN
|
||||||
|
#define NFD_PathSet_FreePath NFD_PathSet_FreePathN
|
||||||
|
#define NFD_PathSet_EnumNext NFD_PathSet_EnumNextN
|
||||||
|
#else
|
||||||
|
typedef nfdu8char_t nfdchar_t;
|
||||||
|
typedef nfdu8filteritem_t nfdfilteritem_t;
|
||||||
|
#define NFD_FreePath NFD_FreePathU8
|
||||||
|
#define NFD_OpenDialog NFD_OpenDialogU8
|
||||||
|
#define NFD_OpenDialogMultiple NFD_OpenDialogMultipleU8
|
||||||
|
#define NFD_SaveDialog NFD_SaveDialogU8
|
||||||
|
#define NFD_PickFolder NFD_PickFolderU8
|
||||||
|
#define NFD_PathSet_GetPath NFD_PathSet_GetPathU8
|
||||||
|
#define NFD_PathSet_FreePath NFD_PathSet_FreePathU8
|
||||||
|
#define NFD_PathSet_EnumNext NFD_PathSet_EnumNextU8
|
||||||
|
#endif // NFD_NATIVE
|
||||||
|
|
||||||
|
#else // _WIN32
|
||||||
|
|
||||||
|
/* the native charset is already UTF-8 */
|
||||||
|
typedef nfdnchar_t nfdchar_t;
|
||||||
|
typedef nfdnfilteritem_t nfdfilteritem_t;
|
||||||
|
#define NFD_FreePath NFD_FreePathN
|
||||||
|
#define NFD_OpenDialog NFD_OpenDialogN
|
||||||
|
#define NFD_OpenDialogMultiple NFD_OpenDialogMultipleN
|
||||||
|
#define NFD_SaveDialog NFD_SaveDialogN
|
||||||
|
#define NFD_PickFolder NFD_PickFolderN
|
||||||
|
#define NFD_PathSet_GetPath NFD_PathSet_GetPathN
|
||||||
|
#define NFD_PathSet_FreePath NFD_PathSet_FreePathN
|
||||||
|
#define NFD_PathSet_EnumNext NFD_PathSet_EnumNextN
|
||||||
|
typedef nfdnchar_t nfdu8char_t;
|
||||||
|
typedef nfdnfilteritem_t nfdu8filteritem_t;
|
||||||
|
#define NFD_FreePathU8 NFD_FreePathN
|
||||||
|
#define NFD_OpenDialogU8 NFD_OpenDialogN
|
||||||
|
#define NFD_OpenDialogMultipleU8 NFD_OpenDialogMultipleN
|
||||||
|
#define NFD_SaveDialogU8 NFD_SaveDialogN
|
||||||
|
#define NFD_PickFolderU8 NFD_PickFolderN
|
||||||
|
#define NFD_PathSet_GetPathU8 NFD_PathSet_GetPathN
|
||||||
|
#define NFD_PathSet_FreePathU8 NFD_PathSet_FreePathN
|
||||||
|
#define NFD_PathSet_EnumNextU8 NFD_PathSet_EnumNextN
|
||||||
|
|
||||||
|
#endif // _WIN32
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif // __cplusplus
|
||||||
|
|
||||||
#endif
|
#endif // _NFD_H
|
||||||
|
515
nfd/nfd_cocoa.m
515
nfd/nfd_cocoa.m
@ -1,286 +1,321 @@
|
|||||||
/*
|
/*
|
||||||
Native File Dialog
|
Native File Dialog Extended
|
||||||
|
Repository: https://github.com/btzy/nativefiledialog-extended
|
||||||
http://www.frogtoss.com/labs
|
License: Zlib
|
||||||
|
Authors: Bernard Teo, Michael Labbe
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <AppKit/AppKit.h>
|
#include <AppKit/AppKit.h>
|
||||||
#include "nfd.h"
|
#include "nfd.h"
|
||||||
#include "nfd_common.h"
|
|
||||||
|
|
||||||
static NSArray *BuildAllowedFileTypes( const char *filterList )
|
static const char* g_errorstr = NULL;
|
||||||
{
|
|
||||||
|
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
|
// Commas and semicolons are the same thing on this platform
|
||||||
|
|
||||||
NSMutableArray *buildFilterList = [[NSMutableArray alloc] init];
|
NSMutableArray* buildFilterList = [[NSMutableArray alloc] init];
|
||||||
|
|
||||||
char typebuf[NFD_MAX_STRLEN] = {0};
|
for (nfdfiltersize_t filterIndex = 0; filterIndex != filterCount; ++filterIndex) {
|
||||||
|
// this is the spec to parse (we don't use the friendly name on OS X)
|
||||||
size_t filterListLen = strlen(filterList);
|
const nfdnchar_t* filterSpec = filterList[filterIndex].spec;
|
||||||
char *p_typebuf = typebuf;
|
|
||||||
for ( size_t i = 0; i < filterListLen+1; ++i )
|
|
||||||
{
|
|
||||||
if ( filterList[i] == ',' || filterList[i] == ';' || filterList[i] == '\0' )
|
|
||||||
{
|
|
||||||
++p_typebuf;
|
|
||||||
*p_typebuf = '\0';
|
|
||||||
NSString *thisType = [NSString stringWithUTF8String: typebuf];
|
|
||||||
[buildFilterList addObject:thisType];
|
|
||||||
p_typebuf = typebuf;
|
|
||||||
*p_typebuf = '\0';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
*p_typebuf = filterList[i];
|
|
||||||
++p_typebuf;
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// add the extension to the array
|
||||||
|
NSString* filterStr = [NSString stringWithUTF8String:p_currentFilterBegin];
|
||||||
|
[buildFilterList addObject:filterStr];
|
||||||
}
|
}
|
||||||
|
|
||||||
NSArray *returnArray = [NSArray arrayWithArray:buildFilterList];
|
NSArray* returnArray = [NSArray arrayWithArray:buildFilterList];
|
||||||
|
|
||||||
[buildFilterList release];
|
[buildFilterList release];
|
||||||
|
|
||||||
|
assert([returnArray count] != 0);
|
||||||
|
|
||||||
return returnArray;
|
return returnArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void AddFilterListToDialog( NSSavePanel *dialog, const char *filterList )
|
static void AddFilterListToDialog(NSSavePanel* dialog,
|
||||||
{
|
const nfdnfilteritem_t* filterList,
|
||||||
if ( !filterList || strlen(filterList) == 0 )
|
nfdfiltersize_t filterCount) {
|
||||||
return;
|
// note: NSOpenPanel inherits from NSSavePanel.
|
||||||
|
|
||||||
NSArray *allowedFileTypes = BuildAllowedFileTypes( filterList );
|
if (!filterCount) return;
|
||||||
if ( [allowedFileTypes count] != 0 )
|
|
||||||
{
|
assert(filterList);
|
||||||
[dialog setAllowedFileTypes:allowedFileTypes];
|
|
||||||
}
|
// make NSArray of file types
|
||||||
|
NSArray* allowedFileTypes = BuildAllowedFileTypes(filterList, filterCount);
|
||||||
|
|
||||||
|
// set it on the dialog
|
||||||
|
[dialog setAllowedFileTypes:allowedFileTypes];
|
||||||
}
|
}
|
||||||
|
|
||||||
static void SetDefaultPath( NSSavePanel *dialog, const nfdchar_t *defaultPath )
|
static void SetDefaultPath(NSSavePanel* dialog, const nfdnchar_t* defaultPath) {
|
||||||
{
|
if (!defaultPath || !*defaultPath) return;
|
||||||
if ( !defaultPath || strlen(defaultPath) == 0 )
|
|
||||||
return;
|
|
||||||
|
|
||||||
NSString *defaultPathString = [NSString stringWithUTF8String: defaultPath];
|
NSString* defaultPathString = [NSString stringWithUTF8String:defaultPath];
|
||||||
NSURL *url = [NSURL fileURLWithPath:defaultPathString isDirectory:YES];
|
NSURL* url = [NSURL fileURLWithPath:defaultPathString isDirectory:YES];
|
||||||
[dialog setDirectoryURL:url];
|
[dialog setDirectoryURL:url];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void SetDefaultName(NSSavePanel* dialog, const nfdnchar_t* defaultName) {
|
||||||
|
if (!defaultName || !*defaultName) return;
|
||||||
|
|
||||||
/* fixme: pathset should be pathSet */
|
NSString* defaultNameString = [NSString stringWithUTF8String:defaultName];
|
||||||
static nfdresult_t AllocPathSet( NSArray *urls, nfdpathset_t *pathset )
|
[dialog setNameFieldStringValue:defaultNameString];
|
||||||
{
|
}
|
||||||
assert(pathset);
|
|
||||||
assert([urls count]);
|
|
||||||
|
|
||||||
pathset->count = (size_t)[urls count];
|
static nfdresult_t CopyUtf8String(const char* utf8Str, nfdnchar_t** out) {
|
||||||
pathset->indices = NFDi_Malloc( sizeof(size_t)*pathset->count );
|
// byte count, not char count
|
||||||
if ( !pathset->indices )
|
size_t len = strlen(utf8Str);
|
||||||
{
|
|
||||||
return NFD_ERROR;
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// count the total space needed for buf
|
return NFD_ERROR;
|
||||||
size_t bufsize = 0;
|
}
|
||||||
for ( NSURL *url in urls )
|
|
||||||
{
|
/* public */
|
||||||
NSString *path = [url path];
|
|
||||||
bufsize += [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
|
const char* NFD_GetError(void) {
|
||||||
|
return g_errorstr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NFD_FreePathN(nfdnchar_t* filePath) {
|
||||||
|
NFDi_Free((void*)filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
static NSApplicationActivationPolicy old_app_policy;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return NFD_OKAY;
|
||||||
|
}
|
||||||
|
|
||||||
pathset->buf = NFDi_Malloc( sizeof(nfdchar_t) * bufsize );
|
/* call this to de-initialize NFD, if NFD_Init returned NFD_OKAY */
|
||||||
if ( !pathset->buf )
|
void NFD_Quit(void) {
|
||||||
{
|
[[NSApplication sharedApplication] setActivationPolicy:old_app_policy];
|
||||||
return NFD_ERROR;
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return focus to the key window (i.e. main window)
|
||||||
|
[keyWindow makeKeyAndOrderFront:nil];
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// fill buf
|
nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths,
|
||||||
nfdchar_t *p_buf = pathset->buf;
|
const nfdnfilteritem_t* filterList,
|
||||||
size_t count = 0;
|
nfdfiltersize_t filterCount,
|
||||||
for ( NSURL *url in urls )
|
const nfdnchar_t* defaultPath) {
|
||||||
{
|
nfdresult_t result = NFD_CANCEL;
|
||||||
NSString *path = [url path];
|
@autoreleasepool {
|
||||||
const nfdchar_t *utf8Path = [path UTF8String];
|
NSWindow* keyWindow = [[NSApplication sharedApplication] keyWindow];
|
||||||
size_t byteLen = [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
|
|
||||||
memcpy( p_buf, utf8Path, byteLen );
|
|
||||||
|
|
||||||
ptrdiff_t index = p_buf - pathset->buf;
|
NSOpenPanel* dialog = [NSOpenPanel openPanel];
|
||||||
assert( index >= 0 );
|
[dialog setAllowsMultipleSelection:YES];
|
||||||
pathset->indices[count] = (size_t)index;
|
|
||||||
|
|
||||||
p_buf += byteLen;
|
// Build the filter list
|
||||||
++count;
|
AddFilterListToDialog(dialog, filterList, filterCount);
|
||||||
|
|
||||||
|
// Set the starting directory
|
||||||
|
SetDefaultPath(dialog, defaultPath);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return focus to the key window (i.e. main window)
|
||||||
|
[keyWindow makeKeyAndOrderFront:nil];
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return focus to the key window (i.e. main window)
|
||||||
|
[keyWindow makeKeyAndOrderFront:nil];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath) {
|
||||||
|
nfdresult_t result = NFD_CANCEL;
|
||||||
|
@autoreleasepool {
|
||||||
|
NSWindow* keyWindow = [[NSApplication sharedApplication] keyWindow];
|
||||||
|
|
||||||
|
NSOpenPanel* dialog = [NSOpenPanel openPanel];
|
||||||
|
[dialog setAllowsMultipleSelection:NO];
|
||||||
|
[dialog setCanChooseDirectories:YES];
|
||||||
|
[dialog setCanCreateDirectories:YES];
|
||||||
|
[dialog setCanChooseFiles:NO];
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return focus to the key window (i.e. main window)
|
||||||
|
[keyWindow makeKeyAndOrderFront:nil];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, nfdpathsetsize_t* count) {
|
||||||
|
const NSArray* urls = (const NSArray*)pathSet;
|
||||||
|
*count = [urls count];
|
||||||
|
return NFD_OKAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
nfdresult_t NFD_PathSet_GetPathN(const nfdpathset_t* pathSet,
|
||||||
|
nfdpathsetsize_t index,
|
||||||
|
nfdnchar_t** outPath) {
|
||||||
|
const NSArray* urls = (const NSArray*)pathSet;
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NFD_PathSet_Free(const nfdpathset_t* pathSet) {
|
||||||
|
const NSArray* urls = (const NSArray*)pathSet;
|
||||||
|
[urls release];
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
return NFD_OKAY;
|
return NFD_OKAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* public */
|
void NFD_PathSet_FreeEnum(nfdpathsetenum_t* enumerator) {
|
||||||
|
NSEnumerator* real_enum = (NSEnumerator*)enumerator->ptr;
|
||||||
|
[real_enum release];
|
||||||
nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList,
|
|
||||||
const nfdchar_t *defaultPath,
|
|
||||||
nfdchar_t **outPath,
|
|
||||||
void* owner )
|
|
||||||
{
|
|
||||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
||||||
|
|
||||||
NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
|
|
||||||
NSOpenPanel *dialog = [NSOpenPanel openPanel];
|
|
||||||
[dialog setAllowsMultipleSelection:NO];
|
|
||||||
|
|
||||||
// Build the filter list
|
|
||||||
AddFilterListToDialog(dialog, filterList);
|
|
||||||
|
|
||||||
// Set the starting directory
|
|
||||||
SetDefaultPath(dialog, defaultPath);
|
|
||||||
|
|
||||||
nfdresult_t nfdResult = NFD_CANCEL;
|
|
||||||
if ( [dialog runModal] == NSModalResponseOK )
|
|
||||||
{
|
|
||||||
NSURL *url = [dialog URL];
|
|
||||||
const char *utf8Path = [[url path] UTF8String];
|
|
||||||
|
|
||||||
// byte count, not char count
|
|
||||||
size_t len = strlen(utf8Path);//NFDi_UTF8_Strlen(utf8Path);
|
|
||||||
|
|
||||||
*outPath = NFDi_Malloc( len+1 );
|
|
||||||
if ( !*outPath )
|
|
||||||
{
|
|
||||||
[pool release];
|
|
||||||
[keyWindow makeKeyAndOrderFront:nil];
|
|
||||||
return NFD_ERROR;
|
|
||||||
}
|
|
||||||
memcpy( *outPath, utf8Path, len+1 ); /* copy null term */
|
|
||||||
nfdResult = NFD_OKAY;
|
|
||||||
}
|
|
||||||
[pool release];
|
|
||||||
|
|
||||||
[keyWindow makeKeyAndOrderFront:nil];
|
|
||||||
return nfdResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nfdresult_t NFD_PathSet_EnumNextN(nfdpathsetenum_t* enumerator, nfdnchar_t** outPath) {
|
||||||
|
NSEnumerator* real_enum = (NSEnumerator*)enumerator->ptr;
|
||||||
|
|
||||||
nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
|
@autoreleasepool {
|
||||||
const nfdchar_t *defaultPath,
|
// autoreleasepool needed because NSURL uses it
|
||||||
nfdpathset_t *outPaths )
|
const NSURL* url = [real_enum nextObject];
|
||||||
{
|
if (url) {
|
||||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
const char* utf8Path = [[url path] UTF8String];
|
||||||
NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
|
return CopyUtf8String(utf8Path, outPath);
|
||||||
|
} else {
|
||||||
NSOpenPanel *dialog = [NSOpenPanel openPanel];
|
*outPath = NULL;
|
||||||
[dialog setAllowsMultipleSelection:YES];
|
return NFD_OKAY;
|
||||||
|
|
||||||
// Build the fiter list.
|
|
||||||
AddFilterListToDialog(dialog, filterList);
|
|
||||||
|
|
||||||
// Set the starting directory
|
|
||||||
SetDefaultPath(dialog, defaultPath);
|
|
||||||
|
|
||||||
nfdresult_t nfdResult = NFD_CANCEL;
|
|
||||||
if ( [dialog runModal] == NSModalResponseOK )
|
|
||||||
{
|
|
||||||
NSArray *urls = [dialog URLs];
|
|
||||||
|
|
||||||
if ( [urls count] == 0 )
|
|
||||||
{
|
|
||||||
[pool release];
|
|
||||||
[keyWindow makeKeyAndOrderFront:nil];
|
|
||||||
return NFD_CANCEL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( AllocPathSet( urls, outPaths ) == NFD_ERROR )
|
|
||||||
{
|
|
||||||
[pool release];
|
|
||||||
[keyWindow makeKeyAndOrderFront:nil];
|
|
||||||
return NFD_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
nfdResult = NFD_OKAY;
|
|
||||||
}
|
}
|
||||||
[pool release];
|
|
||||||
|
|
||||||
[keyWindow makeKeyAndOrderFront:nil];
|
|
||||||
return nfdResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
|
|
||||||
const nfdchar_t *defaultPath,
|
|
||||||
nfdchar_t **outPath,
|
|
||||||
void* owner )
|
|
||||||
{
|
|
||||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
||||||
NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
|
|
||||||
|
|
||||||
NSSavePanel *dialog = [NSSavePanel savePanel];
|
|
||||||
[dialog setExtensionHidden:NO];
|
|
||||||
|
|
||||||
// Build the filter list.
|
|
||||||
AddFilterListToDialog(dialog, filterList);
|
|
||||||
|
|
||||||
// Set the starting directory
|
|
||||||
SetDefaultPath(dialog, defaultPath);
|
|
||||||
|
|
||||||
nfdresult_t nfdResult = NFD_CANCEL;
|
|
||||||
if ( [dialog runModal] == NSModalResponseOK )
|
|
||||||
{
|
|
||||||
NSURL *url = [dialog URL];
|
|
||||||
const char *utf8Path = [[url path] UTF8String];
|
|
||||||
|
|
||||||
size_t byteLen = [url.path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
|
|
||||||
|
|
||||||
*outPath = NFDi_Malloc( byteLen );
|
|
||||||
if ( !*outPath )
|
|
||||||
{
|
|
||||||
[pool release];
|
|
||||||
[keyWindow makeKeyAndOrderFront:nil];
|
|
||||||
return NFD_ERROR;
|
|
||||||
}
|
|
||||||
memcpy( *outPath, utf8Path, byteLen );
|
|
||||||
nfdResult = NFD_OKAY;
|
|
||||||
}
|
|
||||||
|
|
||||||
[pool release];
|
|
||||||
[keyWindow makeKeyAndOrderFront:nil];
|
|
||||||
return nfdResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath,
|
|
||||||
nfdchar_t **outPath)
|
|
||||||
{
|
|
||||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
||||||
|
|
||||||
NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
|
|
||||||
NSOpenPanel *dialog = [NSOpenPanel openPanel];
|
|
||||||
[dialog setAllowsMultipleSelection:NO];
|
|
||||||
[dialog setCanChooseDirectories:YES];
|
|
||||||
[dialog setCanCreateDirectories:YES];
|
|
||||||
[dialog setCanChooseFiles:NO];
|
|
||||||
|
|
||||||
// Set the starting directory
|
|
||||||
SetDefaultPath(dialog, defaultPath);
|
|
||||||
|
|
||||||
nfdresult_t nfdResult = NFD_CANCEL;
|
|
||||||
if ( [dialog runModal] == NSModalResponseOK )
|
|
||||||
{
|
|
||||||
NSURL *url = [dialog URL];
|
|
||||||
const char *utf8Path = [[url path] UTF8String];
|
|
||||||
|
|
||||||
// byte count, not char count
|
|
||||||
size_t len = strlen(utf8Path);//NFDi_UTF8_Strlen(utf8Path);
|
|
||||||
|
|
||||||
*outPath = NFDi_Malloc( len+1 );
|
|
||||||
if ( !*outPath )
|
|
||||||
{
|
|
||||||
[pool release];
|
|
||||||
[keyWindow makeKeyAndOrderFront:nil];
|
|
||||||
return NFD_ERROR;
|
|
||||||
}
|
|
||||||
memcpy( *outPath, utf8Path, len+1 ); /* copy null term */
|
|
||||||
nfdResult = NFD_OKAY;
|
|
||||||
}
|
|
||||||
[pool release];
|
|
||||||
|
|
||||||
[keyWindow makeKeyAndOrderFront:nil];
|
|
||||||
return nfdResult;
|
|
||||||
}
|
}
|
||||||
|
142
nfd/nfd_common.c
142
nfd/nfd_common.c
@ -1,142 +0,0 @@
|
|||||||
/*
|
|
||||||
Native File Dialog
|
|
||||||
|
|
||||||
http://www.frogtoss.com/labs
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include "nfd_common.h"
|
|
||||||
|
|
||||||
static char g_errorstr[NFD_MAX_STRLEN] = {0};
|
|
||||||
|
|
||||||
/* public routines */
|
|
||||||
|
|
||||||
const char *NFD_GetError( void )
|
|
||||||
{
|
|
||||||
return g_errorstr;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t NFD_PathSet_GetCount( const nfdpathset_t *pathset )
|
|
||||||
{
|
|
||||||
assert(pathset);
|
|
||||||
return pathset->count;
|
|
||||||
}
|
|
||||||
|
|
||||||
nfdchar_t *NFD_PathSet_GetPath( const nfdpathset_t *pathset, size_t num )
|
|
||||||
{
|
|
||||||
assert(pathset);
|
|
||||||
assert(num < pathset->count);
|
|
||||||
|
|
||||||
return pathset->buf + pathset->indices[num];
|
|
||||||
}
|
|
||||||
|
|
||||||
void NFD_PathSet_Free( nfdpathset_t *pathset )
|
|
||||||
{
|
|
||||||
assert(pathset);
|
|
||||||
NFDi_Free( pathset->indices );
|
|
||||||
NFDi_Free( pathset->buf );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* internal routines */
|
|
||||||
|
|
||||||
void *NFDi_Malloc( size_t bytes )
|
|
||||||
{
|
|
||||||
void *ptr = malloc(bytes);
|
|
||||||
if ( !ptr )
|
|
||||||
NFDi_SetError("NFDi_Malloc failed.");
|
|
||||||
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NFDi_Free( void *ptr )
|
|
||||||
{
|
|
||||||
assert(ptr);
|
|
||||||
free(ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NFDi_SetError( const char *msg )
|
|
||||||
{
|
|
||||||
int bTruncate = NFDi_SafeStrncpy( g_errorstr, msg, NFD_MAX_STRLEN );
|
|
||||||
assert( !bTruncate ); _NFD_UNUSED(bTruncate);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int NFDi_SafeStrncpy( char *dst, const char *src, size_t maxCopy )
|
|
||||||
{
|
|
||||||
size_t n = maxCopy;
|
|
||||||
char *d = dst;
|
|
||||||
|
|
||||||
assert( src );
|
|
||||||
assert( dst );
|
|
||||||
|
|
||||||
while ( n > 0 && *src != '\0' )
|
|
||||||
{
|
|
||||||
*d++ = *src++;
|
|
||||||
--n;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Truncation case -
|
|
||||||
terminate string and return true */
|
|
||||||
if ( n == 0 )
|
|
||||||
{
|
|
||||||
dst[maxCopy-1] = '\0';
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* No truncation. Append a single NULL and return. */
|
|
||||||
*d = '\0';
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* adapted from microutf8 */
|
|
||||||
size_t NFDi_UTF8_Strlen( const nfdchar_t *str )
|
|
||||||
{
|
|
||||||
/* This function doesn't properly check validity of UTF-8 character
|
|
||||||
sequence, it is supposed to use only with valid UTF-8 strings. */
|
|
||||||
|
|
||||||
size_t character_count = 0;
|
|
||||||
size_t i = 0; /* Counter used to iterate over string. */
|
|
||||||
nfdchar_t maybe_bom[4];
|
|
||||||
|
|
||||||
/* If there is UTF-8 BOM ignore it. */
|
|
||||||
if (strlen(str) > 2)
|
|
||||||
{
|
|
||||||
strncpy(maybe_bom, str, 3);
|
|
||||||
maybe_bom[3] = 0;
|
|
||||||
if (strcmp(maybe_bom, (nfdchar_t*)NFD_UTF8_BOM) == 0)
|
|
||||||
i += 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
while(str[i])
|
|
||||||
{
|
|
||||||
if (str[i] >> 7 == 0)
|
|
||||||
{
|
|
||||||
/* If bit pattern begins with 0 we have ascii character. */
|
|
||||||
++character_count;
|
|
||||||
}
|
|
||||||
else if (str[i] >> 6 == 3)
|
|
||||||
{
|
|
||||||
/* If bit pattern begins with 11 it is beginning of UTF-8 byte sequence. */
|
|
||||||
++character_count;
|
|
||||||
}
|
|
||||||
else if (str[i] >> 6 == 2)
|
|
||||||
; /* If bit pattern begins with 10 it is middle of utf-8 byte sequence. */
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* In any other case this is not valid UTF-8. */
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
|
|
||||||
return character_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
int NFDi_IsFilterSegmentChar( char ch )
|
|
||||||
{
|
|
||||||
return (ch==','||ch==';'||ch=='\0');
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
|||||||
/*
|
|
||||||
Native File Dialog
|
|
||||||
|
|
||||||
Internal, common across platforms
|
|
||||||
|
|
||||||
http://www.frogtoss.com/labs
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef _NFD_COMMON_H
|
|
||||||
#define _NFD_COMMON_H
|
|
||||||
|
|
||||||
#include "nfd.h"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define NFD_MAX_STRLEN 256
|
|
||||||
#define _NFD_UNUSED(x) ((void)x)
|
|
||||||
|
|
||||||
#define NFD_UTF8_BOM "\xEF\xBB\xBF"
|
|
||||||
|
|
||||||
|
|
||||||
void *NFDi_Malloc( size_t bytes );
|
|
||||||
void NFDi_Free( void *ptr );
|
|
||||||
void NFDi_SetError( const char *msg );
|
|
||||||
int NFDi_SafeStrncpy( char *dst, const char *src, size_t maxCopy );
|
|
||||||
size_t NFDi_UTF8_Strlen( const nfdchar_t *str );
|
|
||||||
int NFDi_IsFilterSegmentChar( char ch );
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
381
nfd/nfd_gtk.c
381
nfd/nfd_gtk.c
@ -1,381 +0,0 @@
|
|||||||
/*
|
|
||||||
Native File Dialog
|
|
||||||
|
|
||||||
http://www.frogtoss.com/labs
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <gtk/gtk.h>
|
|
||||||
#include "nfd.h"
|
|
||||||
#include "nfd_common.h"
|
|
||||||
|
|
||||||
|
|
||||||
const char INIT_FAIL_MSG[] = "gtk_init_check failed to initilaize GTK+";
|
|
||||||
|
|
||||||
|
|
||||||
static void AddTypeToFilterName( const char *typebuf, char *filterName, size_t bufsize )
|
|
||||||
{
|
|
||||||
const char SEP[] = ", ";
|
|
||||||
|
|
||||||
size_t len = strlen(filterName);
|
|
||||||
if ( len != 0 )
|
|
||||||
{
|
|
||||||
strncat( filterName, SEP, bufsize - len - 1 );
|
|
||||||
len += strlen(SEP);
|
|
||||||
}
|
|
||||||
|
|
||||||
strncat( filterName, typebuf, bufsize - len - 1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
static void AddFiltersToDialog( GtkWidget *dialog, const char *filterList )
|
|
||||||
{
|
|
||||||
GtkFileFilter *filter;
|
|
||||||
char typebuf[NFD_MAX_STRLEN] = {0};
|
|
||||||
const char *p_filterList = filterList;
|
|
||||||
char *p_typebuf = typebuf;
|
|
||||||
char filterName[NFD_MAX_STRLEN] = {0};
|
|
||||||
|
|
||||||
if ( !filterList || strlen(filterList) == 0 )
|
|
||||||
return;
|
|
||||||
|
|
||||||
filter = gtk_file_filter_new();
|
|
||||||
while ( 1 )
|
|
||||||
{
|
|
||||||
|
|
||||||
if ( NFDi_IsFilterSegmentChar(*p_filterList) )
|
|
||||||
{
|
|
||||||
char typebufWildcard[NFD_MAX_STRLEN];
|
|
||||||
/* add another type to the filter */
|
|
||||||
assert( strlen(typebuf) > 0 );
|
|
||||||
assert( strlen(typebuf) < NFD_MAX_STRLEN-1 );
|
|
||||||
|
|
||||||
snprintf( typebufWildcard, NFD_MAX_STRLEN, "*.%s", typebuf );
|
|
||||||
AddTypeToFilterName( typebuf, filterName, NFD_MAX_STRLEN );
|
|
||||||
|
|
||||||
gtk_file_filter_add_pattern( filter, typebufWildcard );
|
|
||||||
|
|
||||||
p_typebuf = typebuf;
|
|
||||||
memset( typebuf, 0, sizeof(char) * NFD_MAX_STRLEN );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( *p_filterList == ';' || *p_filterList == '\0' )
|
|
||||||
{
|
|
||||||
/* end of filter -- add it to the dialog */
|
|
||||||
|
|
||||||
gtk_file_filter_set_name( filter, filterName );
|
|
||||||
gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter );
|
|
||||||
|
|
||||||
filterName[0] = '\0';
|
|
||||||
|
|
||||||
if ( *p_filterList == '\0' )
|
|
||||||
break;
|
|
||||||
|
|
||||||
filter = gtk_file_filter_new();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( !NFDi_IsFilterSegmentChar( *p_filterList ) )
|
|
||||||
{
|
|
||||||
*p_typebuf = *p_filterList;
|
|
||||||
p_typebuf++;
|
|
||||||
}
|
|
||||||
|
|
||||||
p_filterList++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* always append a wildcard option to the end*/
|
|
||||||
|
|
||||||
filter = gtk_file_filter_new();
|
|
||||||
gtk_file_filter_set_name( filter, "*.*" );
|
|
||||||
gtk_file_filter_add_pattern( filter, "*" );
|
|
||||||
gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter );
|
|
||||||
}
|
|
||||||
|
|
||||||
static void SetDefaultPath( GtkWidget *dialog, const char *defaultPath )
|
|
||||||
{
|
|
||||||
if ( !defaultPath || strlen(defaultPath) == 0 )
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* GTK+ manual recommends not specifically setting the default path.
|
|
||||||
We do it anyway in order to be consistent across platforms.
|
|
||||||
|
|
||||||
If consistency with the native OS is preferred, this is the line
|
|
||||||
to comment out. -ml */
|
|
||||||
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), defaultPath );
|
|
||||||
}
|
|
||||||
|
|
||||||
static nfdresult_t AllocPathSet( GSList *fileList, nfdpathset_t *pathSet )
|
|
||||||
{
|
|
||||||
size_t bufSize = 0;
|
|
||||||
GSList *node;
|
|
||||||
nfdchar_t *p_buf;
|
|
||||||
size_t count = 0;
|
|
||||||
|
|
||||||
assert(fileList);
|
|
||||||
assert(pathSet);
|
|
||||||
|
|
||||||
pathSet->count = (size_t)g_slist_length( fileList );
|
|
||||||
assert( pathSet->count > 0 );
|
|
||||||
|
|
||||||
pathSet->indices = NFDi_Malloc( sizeof(size_t)*pathSet->count );
|
|
||||||
if ( !pathSet->indices )
|
|
||||||
{
|
|
||||||
return NFD_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* count the total space needed for buf */
|
|
||||||
for ( node = fileList; node; node = node->next )
|
|
||||||
{
|
|
||||||
assert(node->data);
|
|
||||||
bufSize += strlen( (const gchar*)node->data ) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pathSet->buf = NFDi_Malloc( sizeof(nfdchar_t) * bufSize );
|
|
||||||
|
|
||||||
/* fill buf */
|
|
||||||
p_buf = pathSet->buf;
|
|
||||||
for ( node = fileList; node; node = node->next )
|
|
||||||
{
|
|
||||||
nfdchar_t *path = (nfdchar_t*)(node->data);
|
|
||||||
size_t byteLen = strlen(path)+1;
|
|
||||||
ptrdiff_t index;
|
|
||||||
|
|
||||||
memcpy( p_buf, path, byteLen );
|
|
||||||
g_free(node->data);
|
|
||||||
|
|
||||||
index = p_buf - pathSet->buf;
|
|
||||||
assert( index >= 0 );
|
|
||||||
pathSet->indices[count] = (size_t)index;
|
|
||||||
|
|
||||||
p_buf += byteLen;
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_slist_free( fileList );
|
|
||||||
|
|
||||||
return NFD_OKAY;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void WaitForCleanup(void)
|
|
||||||
{
|
|
||||||
while (gtk_events_pending())
|
|
||||||
gtk_main_iteration();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* public */
|
|
||||||
|
|
||||||
nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList,
|
|
||||||
const nfdchar_t *defaultPath,
|
|
||||||
nfdchar_t **outPath,
|
|
||||||
void* owner )
|
|
||||||
{
|
|
||||||
GtkWidget *dialog;
|
|
||||||
nfdresult_t result;
|
|
||||||
|
|
||||||
if ( !gtk_init_check( NULL, NULL ) )
|
|
||||||
{
|
|
||||||
NFDi_SetError(INIT_FAIL_MSG);
|
|
||||||
return NFD_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog = gtk_file_chooser_dialog_new( "Open File",
|
|
||||||
NULL,
|
|
||||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
|
||||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
|
||||||
"_Open", GTK_RESPONSE_ACCEPT,
|
|
||||||
NULL );
|
|
||||||
|
|
||||||
/* Build the filter list */
|
|
||||||
AddFiltersToDialog(dialog, filterList);
|
|
||||||
|
|
||||||
/* Set the default path */
|
|
||||||
SetDefaultPath(dialog, defaultPath);
|
|
||||||
|
|
||||||
result = NFD_CANCEL;
|
|
||||||
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
|
|
||||||
{
|
|
||||||
char *filename;
|
|
||||||
|
|
||||||
filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
|
|
||||||
|
|
||||||
{
|
|
||||||
size_t len = strlen(filename);
|
|
||||||
*outPath = NFDi_Malloc( len + 1 );
|
|
||||||
memcpy( *outPath, filename, len + 1 );
|
|
||||||
if ( !*outPath )
|
|
||||||
{
|
|
||||||
g_free( filename );
|
|
||||||
gtk_widget_destroy(dialog);
|
|
||||||
return NFD_ERROR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
g_free( filename );
|
|
||||||
|
|
||||||
result = NFD_OKAY;
|
|
||||||
}
|
|
||||||
|
|
||||||
WaitForCleanup();
|
|
||||||
gtk_widget_destroy(dialog);
|
|
||||||
WaitForCleanup();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
|
|
||||||
const nfdchar_t *defaultPath,
|
|
||||||
nfdpathset_t *outPaths )
|
|
||||||
{
|
|
||||||
GtkWidget *dialog;
|
|
||||||
nfdresult_t result;
|
|
||||||
|
|
||||||
if ( !gtk_init_check( NULL, NULL ) )
|
|
||||||
{
|
|
||||||
NFDi_SetError(INIT_FAIL_MSG);
|
|
||||||
return NFD_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog = gtk_file_chooser_dialog_new( "Open Files",
|
|
||||||
NULL,
|
|
||||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
|
||||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
|
||||||
"_Open", GTK_RESPONSE_ACCEPT,
|
|
||||||
NULL );
|
|
||||||
gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER(dialog), TRUE );
|
|
||||||
|
|
||||||
/* Build the filter list */
|
|
||||||
AddFiltersToDialog(dialog, filterList);
|
|
||||||
|
|
||||||
/* Set the default path */
|
|
||||||
SetDefaultPath(dialog, defaultPath);
|
|
||||||
|
|
||||||
result = NFD_CANCEL;
|
|
||||||
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
|
|
||||||
{
|
|
||||||
GSList *fileList = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER(dialog) );
|
|
||||||
if ( AllocPathSet( fileList, outPaths ) == NFD_ERROR )
|
|
||||||
{
|
|
||||||
gtk_widget_destroy(dialog);
|
|
||||||
return NFD_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = NFD_OKAY;
|
|
||||||
}
|
|
||||||
|
|
||||||
WaitForCleanup();
|
|
||||||
gtk_widget_destroy(dialog);
|
|
||||||
WaitForCleanup();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
|
|
||||||
const nfdchar_t *defaultPath,
|
|
||||||
nfdchar_t **outPath,
|
|
||||||
void* owner )
|
|
||||||
{
|
|
||||||
GtkWidget *dialog;
|
|
||||||
nfdresult_t result;
|
|
||||||
|
|
||||||
if ( !gtk_init_check( NULL, NULL ) )
|
|
||||||
{
|
|
||||||
NFDi_SetError(INIT_FAIL_MSG);
|
|
||||||
return NFD_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog = gtk_file_chooser_dialog_new( "Save File",
|
|
||||||
NULL,
|
|
||||||
GTK_FILE_CHOOSER_ACTION_SAVE,
|
|
||||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
|
||||||
"_Save", GTK_RESPONSE_ACCEPT,
|
|
||||||
NULL );
|
|
||||||
gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE );
|
|
||||||
|
|
||||||
/* Build the filter list */
|
|
||||||
AddFiltersToDialog(dialog, filterList);
|
|
||||||
|
|
||||||
/* Set the default path */
|
|
||||||
SetDefaultPath(dialog, defaultPath);
|
|
||||||
|
|
||||||
result = NFD_CANCEL;
|
|
||||||
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
|
|
||||||
{
|
|
||||||
char *filename;
|
|
||||||
filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
|
|
||||||
|
|
||||||
{
|
|
||||||
size_t len = strlen(filename);
|
|
||||||
*outPath = NFDi_Malloc( len + 1 );
|
|
||||||
memcpy( *outPath, filename, len + 1 );
|
|
||||||
if ( !*outPath )
|
|
||||||
{
|
|
||||||
g_free( filename );
|
|
||||||
gtk_widget_destroy(dialog);
|
|
||||||
return NFD_ERROR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
g_free(filename);
|
|
||||||
|
|
||||||
result = NFD_OKAY;
|
|
||||||
}
|
|
||||||
|
|
||||||
WaitForCleanup();
|
|
||||||
gtk_widget_destroy(dialog);
|
|
||||||
WaitForCleanup();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath,
|
|
||||||
nfdchar_t **outPath)
|
|
||||||
{
|
|
||||||
GtkWidget *dialog;
|
|
||||||
nfdresult_t result;
|
|
||||||
|
|
||||||
if (!gtk_init_check(NULL, NULL))
|
|
||||||
{
|
|
||||||
NFDi_SetError(INIT_FAIL_MSG);
|
|
||||||
return NFD_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog = gtk_file_chooser_dialog_new( "Select folder",
|
|
||||||
NULL,
|
|
||||||
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
|
|
||||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
|
||||||
"_Select", GTK_RESPONSE_ACCEPT,
|
|
||||||
NULL );
|
|
||||||
gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE );
|
|
||||||
|
|
||||||
|
|
||||||
/* Set the default path */
|
|
||||||
SetDefaultPath(dialog, defaultPath);
|
|
||||||
|
|
||||||
result = NFD_CANCEL;
|
|
||||||
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
|
|
||||||
{
|
|
||||||
char *filename;
|
|
||||||
filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
|
|
||||||
|
|
||||||
{
|
|
||||||
size_t len = strlen(filename);
|
|
||||||
*outPath = NFDi_Malloc( len + 1 );
|
|
||||||
memcpy( *outPath, filename, len + 1 );
|
|
||||||
if ( !*outPath )
|
|
||||||
{
|
|
||||||
g_free( filename );
|
|
||||||
gtk_widget_destroy(dialog);
|
|
||||||
return NFD_ERROR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
g_free(filename);
|
|
||||||
|
|
||||||
result = NFD_OKAY;
|
|
||||||
}
|
|
||||||
|
|
||||||
WaitForCleanup();
|
|
||||||
gtk_widget_destroy(dialog);
|
|
||||||
WaitForCleanup();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
631
nfd/nfd_gtk.cpp
Normal file
631
nfd/nfd_gtk.cpp
Normal file
@ -0,0 +1,631 @@
|
|||||||
|
/*
|
||||||
|
Native File Dialog Extended
|
||||||
|
Repository: https://github.com/btzy/nativefiledialog-extended
|
||||||
|
License: Zlib
|
||||||
|
Authors: Bernard Teo, Michael Labbe
|
||||||
|
|
||||||
|
Note: We do not check for malloc failure on Linux - Linux overcommits memory!
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
#if defined(GDK_WINDOWING_X11)
|
||||||
|
#include <gdk/gdkx.h>
|
||||||
|
#endif
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "nfd.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct Free_Guard {
|
||||||
|
T* data;
|
||||||
|
Free_Guard(T* freeable) noexcept : data(freeable) {}
|
||||||
|
~Free_Guard() { NFDi_Free(data); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct FreeCheck_Guard {
|
||||||
|
T* data;
|
||||||
|
FreeCheck_Guard(T* freeable = nullptr) noexcept : data(freeable) {}
|
||||||
|
~FreeCheck_Guard() {
|
||||||
|
if (data) NFDi_Free(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* current error */
|
||||||
|
const char* g_errorstr = nullptr;
|
||||||
|
|
||||||
|
void NFDi_SetError(const char* msg) {
|
||||||
|
g_errorstr = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T = void>
|
||||||
|
T* NFDi_Malloc(size_t bytes) {
|
||||||
|
void* ptr = malloc(bytes);
|
||||||
|
if (!ptr) NFDi_SetError("NFDi_Malloc failed.");
|
||||||
|
|
||||||
|
return static_cast<T*>(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void NFDi_Free(T* ptr) {
|
||||||
|
assert(ptr);
|
||||||
|
free(static_cast<void*>(ptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T* copy(const T* begin, const T* end, T* out) {
|
||||||
|
for (; begin != end; ++begin) {
|
||||||
|
*out++ = *begin;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does not own the filter and extension.
|
||||||
|
struct Pair_GtkFileFilter_FileExtension {
|
||||||
|
GtkFileFilter* filter;
|
||||||
|
const nfdnchar_t* extensionBegin;
|
||||||
|
const nfdnchar_t* extensionEnd;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ButtonClickedArgs {
|
||||||
|
Pair_GtkFileFilter_FileExtension* map;
|
||||||
|
GtkFileChooser* chooser;
|
||||||
|
};
|
||||||
|
|
||||||
|
void AddFiltersToDialog(GtkFileChooser* chooser,
|
||||||
|
const nfdnfilteritem_t* filterList,
|
||||||
|
nfdfiltersize_t filterCount) {
|
||||||
|
if (filterCount) {
|
||||||
|
assert(filterList);
|
||||||
|
|
||||||
|
// we have filters to add ... format and add them
|
||||||
|
|
||||||
|
for (nfdfiltersize_t index = 0; index != filterCount; ++index) {
|
||||||
|
GtkFileFilter* filter = gtk_file_filter_new();
|
||||||
|
|
||||||
|
// count number of file extensions
|
||||||
|
size_t sep = 1;
|
||||||
|
for (const nfdnchar_t* p_spec = filterList[index].spec; *p_spec; ++p_spec) {
|
||||||
|
if (*p_spec == L',') {
|
||||||
|
++sep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// friendly name conversions: "png,jpg" -> "Image files
|
||||||
|
// (png, jpg)"
|
||||||
|
|
||||||
|
// calculate space needed (including the trailing '\0')
|
||||||
|
size_t nameSize =
|
||||||
|
sep + strlen(filterList[index].spec) + 3 + strlen(filterList[index].name);
|
||||||
|
|
||||||
|
// malloc the required memory
|
||||||
|
nfdnchar_t* nameBuf = NFDi_Malloc<nfdnchar_t>(sizeof(nfdnchar_t) * nameSize);
|
||||||
|
|
||||||
|
nfdnchar_t* p_nameBuf = nameBuf;
|
||||||
|
for (const nfdnchar_t* p_filterName = filterList[index].name; *p_filterName;
|
||||||
|
++p_filterName) {
|
||||||
|
*p_nameBuf++ = *p_filterName;
|
||||||
|
}
|
||||||
|
*p_nameBuf++ = ' ';
|
||||||
|
*p_nameBuf++ = '(';
|
||||||
|
const nfdnchar_t* p_extensionStart = filterList[index].spec;
|
||||||
|
for (const nfdnchar_t* p_spec = filterList[index].spec; true; ++p_spec) {
|
||||||
|
if (*p_spec == ',' || !*p_spec) {
|
||||||
|
if (*p_spec == ',') {
|
||||||
|
*p_nameBuf++ = ',';
|
||||||
|
*p_nameBuf++ = ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
// +1 for the trailing '\0'
|
||||||
|
nfdnchar_t* extnBuf = NFDi_Malloc<nfdnchar_t>(sizeof(nfdnchar_t) *
|
||||||
|
(p_spec - p_extensionStart + 3));
|
||||||
|
nfdnchar_t* p_extnBufEnd = extnBuf;
|
||||||
|
*p_extnBufEnd++ = '*';
|
||||||
|
*p_extnBufEnd++ = '.';
|
||||||
|
p_extnBufEnd = copy(p_extensionStart, p_spec, p_extnBufEnd);
|
||||||
|
*p_extnBufEnd++ = '\0';
|
||||||
|
assert((size_t)(p_extnBufEnd - extnBuf) ==
|
||||||
|
sizeof(nfdnchar_t) * (p_spec - p_extensionStart + 3));
|
||||||
|
gtk_file_filter_add_pattern(filter, extnBuf);
|
||||||
|
NFDi_Free(extnBuf);
|
||||||
|
|
||||||
|
if (*p_spec) {
|
||||||
|
// update the extension start point
|
||||||
|
p_extensionStart = p_spec + 1;
|
||||||
|
} else {
|
||||||
|
// reached the '\0' character
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*p_nameBuf++ = *p_spec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*p_nameBuf++ = ')';
|
||||||
|
*p_nameBuf++ = '\0';
|
||||||
|
assert((size_t)(p_nameBuf - nameBuf) == sizeof(nfdnchar_t) * nameSize);
|
||||||
|
|
||||||
|
// add to the filter
|
||||||
|
gtk_file_filter_set_name(filter, nameBuf);
|
||||||
|
|
||||||
|
// free the memory
|
||||||
|
NFDi_Free(nameBuf);
|
||||||
|
|
||||||
|
// add filter to chooser
|
||||||
|
gtk_file_chooser_add_filter(chooser, filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* always append a wildcard option to the end*/
|
||||||
|
|
||||||
|
GtkFileFilter* filter = gtk_file_filter_new();
|
||||||
|
gtk_file_filter_set_name(filter, "All files");
|
||||||
|
gtk_file_filter_add_pattern(filter, "*");
|
||||||
|
gtk_file_chooser_add_filter(chooser, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns null-terminated map (trailing .filter is null)
|
||||||
|
Pair_GtkFileFilter_FileExtension* AddFiltersToDialogWithMap(GtkFileChooser* chooser,
|
||||||
|
const nfdnfilteritem_t* filterList,
|
||||||
|
nfdfiltersize_t filterCount) {
|
||||||
|
Pair_GtkFileFilter_FileExtension* map = NFDi_Malloc<Pair_GtkFileFilter_FileExtension>(
|
||||||
|
sizeof(Pair_GtkFileFilter_FileExtension) * (filterCount + 1));
|
||||||
|
|
||||||
|
if (filterCount) {
|
||||||
|
assert(filterList);
|
||||||
|
|
||||||
|
// we have filters to add ... format and add them
|
||||||
|
|
||||||
|
for (nfdfiltersize_t index = 0; index != filterCount; ++index) {
|
||||||
|
GtkFileFilter* filter = gtk_file_filter_new();
|
||||||
|
|
||||||
|
// store filter in map
|
||||||
|
map[index].filter = filter;
|
||||||
|
map[index].extensionBegin = filterList[index].spec;
|
||||||
|
map[index].extensionEnd = nullptr;
|
||||||
|
|
||||||
|
// count number of file extensions
|
||||||
|
size_t sep = 1;
|
||||||
|
for (const nfdnchar_t* p_spec = filterList[index].spec; *p_spec; ++p_spec) {
|
||||||
|
if (*p_spec == L',') {
|
||||||
|
++sep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// friendly name conversions: "png,jpg" -> "Image files
|
||||||
|
// (png, jpg)"
|
||||||
|
|
||||||
|
// calculate space needed (including the trailing '\0')
|
||||||
|
size_t nameSize =
|
||||||
|
sep + strlen(filterList[index].spec) + 3 + strlen(filterList[index].name);
|
||||||
|
|
||||||
|
// malloc the required memory
|
||||||
|
nfdnchar_t* nameBuf = NFDi_Malloc<nfdnchar_t>(sizeof(nfdnchar_t) * nameSize);
|
||||||
|
|
||||||
|
nfdnchar_t* p_nameBuf = nameBuf;
|
||||||
|
for (const nfdnchar_t* p_filterName = filterList[index].name; *p_filterName;
|
||||||
|
++p_filterName) {
|
||||||
|
*p_nameBuf++ = *p_filterName;
|
||||||
|
}
|
||||||
|
*p_nameBuf++ = ' ';
|
||||||
|
*p_nameBuf++ = '(';
|
||||||
|
const nfdnchar_t* p_extensionStart = filterList[index].spec;
|
||||||
|
for (const nfdnchar_t* p_spec = filterList[index].spec; true; ++p_spec) {
|
||||||
|
if (*p_spec == ',' || !*p_spec) {
|
||||||
|
if (*p_spec == ',') {
|
||||||
|
*p_nameBuf++ = ',';
|
||||||
|
*p_nameBuf++ = ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
// +1 for the trailing '\0'
|
||||||
|
nfdnchar_t* extnBuf = NFDi_Malloc<nfdnchar_t>(sizeof(nfdnchar_t) *
|
||||||
|
(p_spec - p_extensionStart + 3));
|
||||||
|
nfdnchar_t* p_extnBufEnd = extnBuf;
|
||||||
|
*p_extnBufEnd++ = '*';
|
||||||
|
*p_extnBufEnd++ = '.';
|
||||||
|
p_extnBufEnd = copy(p_extensionStart, p_spec, p_extnBufEnd);
|
||||||
|
*p_extnBufEnd++ = '\0';
|
||||||
|
assert((size_t)(p_extnBufEnd - extnBuf) ==
|
||||||
|
sizeof(nfdnchar_t) * (p_spec - p_extensionStart + 3));
|
||||||
|
gtk_file_filter_add_pattern(filter, extnBuf);
|
||||||
|
NFDi_Free(extnBuf);
|
||||||
|
|
||||||
|
// store current pointer in map (if it's
|
||||||
|
// the first one)
|
||||||
|
if (map[index].extensionEnd == nullptr) {
|
||||||
|
map[index].extensionEnd = p_spec;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*p_spec) {
|
||||||
|
// update the extension start point
|
||||||
|
p_extensionStart = p_spec + 1;
|
||||||
|
} else {
|
||||||
|
// reached the '\0' character
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*p_nameBuf++ = *p_spec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*p_nameBuf++ = ')';
|
||||||
|
*p_nameBuf++ = '\0';
|
||||||
|
assert((size_t)(p_nameBuf - nameBuf) == sizeof(nfdnchar_t) * nameSize);
|
||||||
|
|
||||||
|
// add to the filter
|
||||||
|
gtk_file_filter_set_name(filter, nameBuf);
|
||||||
|
|
||||||
|
// free the memory
|
||||||
|
NFDi_Free(nameBuf);
|
||||||
|
|
||||||
|
// add filter to chooser
|
||||||
|
gtk_file_chooser_add_filter(chooser, filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// set trailing map index to null
|
||||||
|
map[filterCount].filter = nullptr;
|
||||||
|
|
||||||
|
/* always append a wildcard option to the end*/
|
||||||
|
GtkFileFilter* filter = gtk_file_filter_new();
|
||||||
|
gtk_file_filter_set_name(filter, "All files");
|
||||||
|
gtk_file_filter_add_pattern(filter, "*");
|
||||||
|
gtk_file_chooser_add_filter(chooser, filter);
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetDefaultPath(GtkFileChooser* chooser, const char* defaultPath) {
|
||||||
|
if (!defaultPath || !*defaultPath) return;
|
||||||
|
|
||||||
|
/* GTK+ manual recommends not specifically setting the default path.
|
||||||
|
We do it anyway in order to be consistent across platforms.
|
||||||
|
|
||||||
|
If consistency with the native OS is preferred, this is the line
|
||||||
|
to comment out. -ml */
|
||||||
|
gtk_file_chooser_set_current_folder(chooser, defaultPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetDefaultName(GtkFileChooser* chooser, const char* defaultName) {
|
||||||
|
if (!defaultName || !*defaultName) return;
|
||||||
|
|
||||||
|
gtk_file_chooser_set_current_name(chooser, defaultName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaitForCleanup() {
|
||||||
|
while (gtk_events_pending()) gtk_main_iteration();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Widget_Guard {
|
||||||
|
GtkWidget* data;
|
||||||
|
Widget_Guard(GtkWidget* widget) : data(widget) {}
|
||||||
|
~Widget_Guard() {
|
||||||
|
WaitForCleanup();
|
||||||
|
gtk_widget_destroy(data);
|
||||||
|
WaitForCleanup();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void FileActivatedSignalHandler(GtkButton* saveButton, void* userdata) {
|
||||||
|
(void)saveButton; // silence the unused arg warning
|
||||||
|
|
||||||
|
ButtonClickedArgs* args = static_cast<ButtonClickedArgs*>(userdata);
|
||||||
|
GtkFileChooser* chooser = args->chooser;
|
||||||
|
char* currentFileName = gtk_file_chooser_get_current_name(chooser);
|
||||||
|
if (*currentFileName) { // string is not empty
|
||||||
|
|
||||||
|
// find a '.' in the file name
|
||||||
|
const char* p_period = currentFileName;
|
||||||
|
for (; *p_period; ++p_period) {
|
||||||
|
if (*p_period == '.') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!*p_period) { // there is no '.', so append the default extension
|
||||||
|
Pair_GtkFileFilter_FileExtension* filterMap =
|
||||||
|
static_cast<Pair_GtkFileFilter_FileExtension*>(args->map);
|
||||||
|
GtkFileFilter* currentFilter = gtk_file_chooser_get_filter(chooser);
|
||||||
|
if (currentFilter) {
|
||||||
|
for (; filterMap->filter; ++filterMap) {
|
||||||
|
if (filterMap->filter == currentFilter) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (filterMap->filter) {
|
||||||
|
// memory for appended string (including '.' and
|
||||||
|
// trailing '\0')
|
||||||
|
char* appendedFileName = NFDi_Malloc<char>(
|
||||||
|
sizeof(char) * ((p_period - currentFileName) +
|
||||||
|
(filterMap->extensionEnd - filterMap->extensionBegin) + 2));
|
||||||
|
char* p_fileName = copy(currentFileName, p_period, appendedFileName);
|
||||||
|
*p_fileName++ = '.';
|
||||||
|
p_fileName = copy(filterMap->extensionBegin, filterMap->extensionEnd, p_fileName);
|
||||||
|
*p_fileName++ = '\0';
|
||||||
|
|
||||||
|
assert(p_fileName - appendedFileName ==
|
||||||
|
(p_period - currentFileName) +
|
||||||
|
(filterMap->extensionEnd - filterMap->extensionBegin) + 2);
|
||||||
|
|
||||||
|
// set the appended file name
|
||||||
|
gtk_file_chooser_set_current_name(chooser, appendedFileName);
|
||||||
|
|
||||||
|
// free the memory
|
||||||
|
NFDi_Free(appendedFileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// free the memory
|
||||||
|
g_free(currentFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapper for gtk_dialog_run() that brings the dialog to the front
|
||||||
|
// see issues at:
|
||||||
|
// https://github.com/btzy/nativefiledialog-extended/issues/31
|
||||||
|
// https://github.com/mlabbe/nativefiledialog/pull/92
|
||||||
|
// https://github.com/guillaumechereau/noc/pull/11
|
||||||
|
gint RunDialogWithFocus(GtkDialog* dialog) {
|
||||||
|
#if defined(GDK_WINDOWING_X11)
|
||||||
|
gtk_widget_show_all(GTK_WIDGET(dialog)); // show the dialog so that it gets a display
|
||||||
|
if (GDK_IS_X11_DISPLAY(gtk_widget_get_display(GTK_WIDGET(dialog)))) {
|
||||||
|
GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(dialog));
|
||||||
|
gdk_window_set_events(
|
||||||
|
window,
|
||||||
|
static_cast<GdkEventMask>(gdk_window_get_events(window) | GDK_PROPERTY_CHANGE_MASK));
|
||||||
|
gtk_window_present_with_time(GTK_WINDOW(dialog), gdk_x11_get_server_time(window));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return gtk_dialog_run(dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
const char* NFD_GetError(void) {
|
||||||
|
return g_errorstr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NFD_ClearError(void) {
|
||||||
|
NFDi_SetError(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* public */
|
||||||
|
|
||||||
|
nfdresult_t NFD_Init(void) {
|
||||||
|
// Init GTK
|
||||||
|
if (!gtk_init_check(NULL, NULL)) {
|
||||||
|
NFDi_SetError("Failed to initialize GTK+ with gtk_init_check.");
|
||||||
|
return NFD_ERROR;
|
||||||
|
}
|
||||||
|
return NFD_OKAY;
|
||||||
|
}
|
||||||
|
void NFD_Quit(void) {
|
||||||
|
// do nothing, GTK cannot be de-initialized
|
||||||
|
}
|
||||||
|
|
||||||
|
void NFD_FreePathN(nfdnchar_t* filePath) {
|
||||||
|
assert(filePath);
|
||||||
|
g_free(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
nfdresult_t NFD_OpenDialogN(nfdnchar_t** outPath,
|
||||||
|
const nfdnfilteritem_t* filterList,
|
||||||
|
nfdfiltersize_t filterCount,
|
||||||
|
const nfdnchar_t* defaultPath) {
|
||||||
|
GtkWidget* widget = gtk_file_chooser_dialog_new("Open File",
|
||||||
|
nullptr,
|
||||||
|
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||||
|
"_Cancel",
|
||||||
|
GTK_RESPONSE_CANCEL,
|
||||||
|
"_Open",
|
||||||
|
GTK_RESPONSE_ACCEPT,
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
// guard to destroy the widget when returning from this function
|
||||||
|
Widget_Guard widgetGuard(widget);
|
||||||
|
|
||||||
|
/* Build the filter list */
|
||||||
|
AddFiltersToDialog(GTK_FILE_CHOOSER(widget), filterList, filterCount);
|
||||||
|
|
||||||
|
/* Set the default path */
|
||||||
|
SetDefaultPath(GTK_FILE_CHOOSER(widget), defaultPath);
|
||||||
|
|
||||||
|
if (RunDialogWithFocus(GTK_DIALOG(widget)) == GTK_RESPONSE_ACCEPT) {
|
||||||
|
// write out the file name
|
||||||
|
*outPath = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
|
||||||
|
|
||||||
|
return NFD_OKAY;
|
||||||
|
} else {
|
||||||
|
return NFD_CANCEL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths,
|
||||||
|
const nfdnfilteritem_t* filterList,
|
||||||
|
nfdfiltersize_t filterCount,
|
||||||
|
const nfdnchar_t* defaultPath) {
|
||||||
|
GtkWidget* widget = gtk_file_chooser_dialog_new("Open Files",
|
||||||
|
nullptr,
|
||||||
|
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||||
|
"_Cancel",
|
||||||
|
GTK_RESPONSE_CANCEL,
|
||||||
|
"_Open",
|
||||||
|
GTK_RESPONSE_ACCEPT,
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
// guard to destroy the widget when returning from this function
|
||||||
|
Widget_Guard widgetGuard(widget);
|
||||||
|
|
||||||
|
// set select multiple
|
||||||
|
gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(widget), TRUE);
|
||||||
|
|
||||||
|
/* Build the filter list */
|
||||||
|
AddFiltersToDialog(GTK_FILE_CHOOSER(widget), filterList, filterCount);
|
||||||
|
|
||||||
|
/* Set the default path */
|
||||||
|
SetDefaultPath(GTK_FILE_CHOOSER(widget), defaultPath);
|
||||||
|
|
||||||
|
if (RunDialogWithFocus(GTK_DIALOG(widget)) == GTK_RESPONSE_ACCEPT) {
|
||||||
|
// write out the file name
|
||||||
|
GSList* fileList = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(widget));
|
||||||
|
|
||||||
|
*outPaths = static_cast<void*>(fileList);
|
||||||
|
return NFD_OKAY;
|
||||||
|
} else {
|
||||||
|
return NFD_CANCEL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath,
|
||||||
|
const nfdnfilteritem_t* filterList,
|
||||||
|
nfdfiltersize_t filterCount,
|
||||||
|
const nfdnchar_t* defaultPath,
|
||||||
|
const nfdnchar_t* defaultName) {
|
||||||
|
GtkWidget* widget = gtk_file_chooser_dialog_new("Save File",
|
||||||
|
nullptr,
|
||||||
|
GTK_FILE_CHOOSER_ACTION_SAVE,
|
||||||
|
"_Cancel",
|
||||||
|
GTK_RESPONSE_CANCEL,
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
// guard to destroy the widget when returning from this function
|
||||||
|
Widget_Guard widgetGuard(widget);
|
||||||
|
|
||||||
|
GtkWidget* saveButton = gtk_dialog_add_button(GTK_DIALOG(widget), "_Save", GTK_RESPONSE_ACCEPT);
|
||||||
|
|
||||||
|
// Prompt on overwrite
|
||||||
|
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(widget), TRUE);
|
||||||
|
|
||||||
|
/* Build the filter list */
|
||||||
|
ButtonClickedArgs buttonClickedArgs;
|
||||||
|
buttonClickedArgs.chooser = GTK_FILE_CHOOSER(widget);
|
||||||
|
buttonClickedArgs.map =
|
||||||
|
AddFiltersToDialogWithMap(GTK_FILE_CHOOSER(widget), filterList, filterCount);
|
||||||
|
|
||||||
|
/* Set the default path */
|
||||||
|
SetDefaultPath(GTK_FILE_CHOOSER(widget), defaultPath);
|
||||||
|
|
||||||
|
/* Set the default file name */
|
||||||
|
SetDefaultName(GTK_FILE_CHOOSER(widget), defaultName);
|
||||||
|
|
||||||
|
/* set the handler to add file extension */
|
||||||
|
gulong handlerID = g_signal_connect(G_OBJECT(saveButton),
|
||||||
|
"pressed",
|
||||||
|
G_CALLBACK(FileActivatedSignalHandler),
|
||||||
|
static_cast<void*>(&buttonClickedArgs));
|
||||||
|
|
||||||
|
/* invoke the dialog (blocks until dialog is closed) */
|
||||||
|
gint result = RunDialogWithFocus(GTK_DIALOG(widget));
|
||||||
|
/* unset the handler */
|
||||||
|
g_signal_handler_disconnect(G_OBJECT(saveButton), handlerID);
|
||||||
|
|
||||||
|
/* free the filter map */
|
||||||
|
NFDi_Free(buttonClickedArgs.map);
|
||||||
|
|
||||||
|
if (result == GTK_RESPONSE_ACCEPT) {
|
||||||
|
// write out the file name
|
||||||
|
*outPath = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
|
||||||
|
|
||||||
|
return NFD_OKAY;
|
||||||
|
} else {
|
||||||
|
return NFD_CANCEL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath) {
|
||||||
|
GtkWidget* widget = gtk_file_chooser_dialog_new("Select folder",
|
||||||
|
nullptr,
|
||||||
|
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
|
||||||
|
"_Cancel",
|
||||||
|
GTK_RESPONSE_CANCEL,
|
||||||
|
"_Select",
|
||||||
|
GTK_RESPONSE_ACCEPT,
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
// guard to destroy the widget when returning from this function
|
||||||
|
Widget_Guard widgetGuard(widget);
|
||||||
|
|
||||||
|
/* Set the default path */
|
||||||
|
SetDefaultPath(GTK_FILE_CHOOSER(widget), defaultPath);
|
||||||
|
|
||||||
|
if (RunDialogWithFocus(GTK_DIALOG(widget)) == GTK_RESPONSE_ACCEPT) {
|
||||||
|
// write out the file name
|
||||||
|
*outPath = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
|
||||||
|
|
||||||
|
return NFD_OKAY;
|
||||||
|
} else {
|
||||||
|
return NFD_CANCEL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, nfdpathsetsize_t* count) {
|
||||||
|
assert(pathSet);
|
||||||
|
// const_cast because methods on GSList aren't const, but it should act
|
||||||
|
// like const to the caller
|
||||||
|
GSList* fileList = const_cast<GSList*>(static_cast<const GSList*>(pathSet));
|
||||||
|
|
||||||
|
*count = g_slist_length(fileList);
|
||||||
|
return NFD_OKAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
nfdresult_t NFD_PathSet_GetPathN(const nfdpathset_t* pathSet,
|
||||||
|
nfdpathsetsize_t index,
|
||||||
|
nfdnchar_t** outPath) {
|
||||||
|
assert(pathSet);
|
||||||
|
// const_cast because methods on GSList aren't const, but it should act
|
||||||
|
// like const to the caller
|
||||||
|
GSList* fileList = const_cast<GSList*>(static_cast<const GSList*>(pathSet));
|
||||||
|
|
||||||
|
// Note: this takes linear time... but should be good enough
|
||||||
|
*outPath = static_cast<nfdnchar_t*>(g_slist_nth_data(fileList, index));
|
||||||
|
|
||||||
|
return NFD_OKAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NFD_PathSet_FreePathN(const nfdnchar_t* filePath) {
|
||||||
|
assert(filePath);
|
||||||
|
(void)filePath; // prevent warning in release build
|
||||||
|
// no-op, because NFD_PathSet_Free does the freeing for us
|
||||||
|
}
|
||||||
|
|
||||||
|
void NFD_PathSet_Free(const nfdpathset_t* pathSet) {
|
||||||
|
assert(pathSet);
|
||||||
|
// const_cast because methods on GSList aren't const, but it should act
|
||||||
|
// like const to the caller
|
||||||
|
GSList* fileList = const_cast<GSList*>(static_cast<const GSList*>(pathSet));
|
||||||
|
|
||||||
|
// free all the nodes
|
||||||
|
for (GSList* node = fileList; node; node = node->next) {
|
||||||
|
assert(node->data);
|
||||||
|
g_free(node->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// free the path set memory
|
||||||
|
g_slist_free(fileList);
|
||||||
|
}
|
||||||
|
|
||||||
|
nfdresult_t NFD_PathSet_GetEnum(const nfdpathset_t* pathSet, nfdpathsetenum_t* outEnumerator) {
|
||||||
|
// The pathset (GSList) is already a linked list, so the enumeration is itself
|
||||||
|
outEnumerator->ptr = const_cast<void*>(pathSet);
|
||||||
|
|
||||||
|
return NFD_OKAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NFD_PathSet_FreeEnum(nfdpathsetenum_t*) {
|
||||||
|
// Do nothing, because the enumeration is the pathset itself
|
||||||
|
}
|
||||||
|
|
||||||
|
nfdresult_t NFD_PathSet_EnumNextN(nfdpathsetenum_t* enumerator, nfdnchar_t** outPath) {
|
||||||
|
const GSList* fileList = static_cast<const GSList*>(enumerator->ptr);
|
||||||
|
|
||||||
|
if (fileList) {
|
||||||
|
*outPath = static_cast<nfdnchar_t*>(fileList->data);
|
||||||
|
enumerator->ptr = static_cast<void*>(fileList->next);
|
||||||
|
} else {
|
||||||
|
*outPath = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NFD_OKAY;
|
||||||
|
}
|
1414
nfd/nfd_portal.cpp
Normal file
1414
nfd/nfd_portal.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1477
nfd/nfd_win.cpp
1477
nfd/nfd_win.cpp
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user