Use NFD extended

This commit is contained in:
Christian Treffs 2021-11-05 07:01:59 +01:00
parent 2667bf19ae
commit e1e5f5de06
No known key found for this signature in database
GPG Key ID: 49A4B4B460BE3ED4
15 changed files with 2500 additions and 2168 deletions

3
.gitmodules vendored
View File

@ -1,3 +1,6 @@
[submodule "3rdparty/nativefiledialog"]
path = 3rdparty/nativefiledialog
url = https://github.com/mlabbe/nativefiledialog.git
[submodule "3rdparty/nativefiledialog-extended"]
path = 3rdparty/nativefiledialog-extended
url = git@github.com:btzy/nativefiledialog-extended.git

1
3rdparty/nativefiledialog-extended vendored Submodule

@ -0,0 +1 @@
Subproject commit 800e880d335bc5af1fef757eac835161c7b93102

View File

@ -1,7 +1,11 @@
NFD_ROOT = 3rdparty/nativefiledialog
NFD_ROOT = 3rdparty/nativefiledialog-extended
CLIB_ROOT = Sources/CNFD
.PHONY: copyLibFiles
copyLibFiles:
cp -r "${NFD_ROOT}/src/" "${CLIB_ROOT}/"
cp "${NFD_ROOT}/LICENSE" "${CLIB_ROOT}/"
cp "${NFD_ROOT}/LICENSE" "${CLIB_ROOT}/"
.PHONY: clearLibFiles
clearLibFiles:
rm -rdf ${CLIB_ROOT}/*

View File

@ -0,0 +1,57 @@
set(TARGET_NAME nfd)
set(PUBLIC_HEADER_FILES
include/nfd.h
include/nfd.hpp)
set(SOURCE_FILES ${PUBLIC_HEADER_FILES})
if(nfd_PLATFORM STREQUAL PLATFORM_WIN32)
list(APPEND SOURCE_FILES nfd_win.cpp)
endif()
if(nfd_PLATFORM STREQUAL PLATFORM_LINUX)
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
message("Using GTK version: ${GTK3_VERSION}")
list(APPEND SOURCE_FILES nfd_gtk.cpp)
endif()
if(nfd_PLATFORM STREQUAL PLATFORM_MACOS)
find_library(APPKIT_LIBRARY AppKit)
list(APPEND SOURCE_FILES nfd_cocoa.m)
endif()
# Define the library
add_library(${TARGET_NAME} STATIC
${SOURCE_FILES})
# Allow includes from include/
target_include_directories(${TARGET_NAME}
PUBLIC include/)
if(nfd_PLATFORM STREQUAL PLATFORM_LINUX)
target_include_directories(${TARGET_NAME}
PRIVATE ${GTK3_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME}
PRIVATE ${GTK3_LIBRARIES})
endif()
if(nfd_PLATFORM STREQUAL PLATFORM_MACOS)
target_link_libraries(${TARGET_NAME}
PRIVATE ${APPKIT_LIBRARY})
endif()
if(nfd_COMPILER STREQUAL COMPILER_MSVC)
string(REPLACE "/EHsc" "/EHs-c-" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
string(REPLACE "/GR" "/GR-" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
set_property(TARGET ${TARGET_NAME} APPEND_STRING PROPERTY STATIC_LIBRARY_OPTIONS /NODEFAULTLIB)
endif()
if(nfd_COMPILER STREQUAL COMPILER_GNU)
target_compile_options(${TARGET_NAME} PRIVATE -nostdlib -fno-exceptions -fno-rtti)
endif()
set_target_properties(${TARGET_NAME} PROPERTIES PUBLIC_HEADER "${PUBLIC_HEADER_FILES}")
install(TARGETS ${TARGET_NAME} LIBRARY DESTINATION lib PUBLIC_HEADER DESTINATION include)

View File

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

View File

@ -1,74 +1,264 @@
/*
Native File Dialog
Native File Dialog Extended
Repository: https://github.com/btzy/nativefiledialog-extended
License: Zlib
Authors: Bernard Teo, Michael Labbe
User API
http://www.frogtoss.com/labs
This header contains the functions that can be called by user code.
*/
#ifndef _NFD_H
#define _NFD_H
#ifdef __cplusplus
extern "C" {
#endif
#endif // __cplusplus
#include <stddef.h>
#ifdef _WIN32
/* denotes UTF-16 char */
typedef wchar_t nfdnchar_t;
#else
/* denotes UTF-8 char */
typedef char nfdchar_t;
typedef char nfdnchar_t;
#endif // _WIN32
/* opaque data structure -- see NFD_PathSet_* */
typedef void nfdpathset_t;
typedef struct {
nfdchar_t *buf;
size_t *indices; /* byte offsets into buf */
size_t count; /* number of indices into buf */
}nfdpathset_t;
void* ptr;
} nfdpathsetenum_t;
typedef unsigned int nfdfiltersize_t;
typedef enum {
NFD_ERROR, /* programmatic error */
NFD_OKAY, /* user pressed okay, or successful return */
NFD_CANCEL /* user pressed cancel */
}nfdresult_t;
NFD_ERROR, /* programmatic error */
NFD_OKAY, /* user pressed okay, or successful return */
NFD_CANCEL /* user pressed cancel */
} nfdresult_t;
typedef struct {
const nfdnchar_t* name;
const nfdnchar_t* spec;
} nfdnfilteritem_t;
/* nfd_<targetplatform>.c */
/* single file open dialog */
nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath );
/* 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);
/* multiple file open dialog */
nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdpathset_t *outPaths );
/* 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 */
/* It is the caller's responsibility to free `outPath` via NFD_FreePathN() if this function returns
* NFD_OKAY */
/* If filterCount is zero, filterList is ignored (you can use NULL) */
/* 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 */
/* It is the caller's responsibility to free `outPaths` via NFD_PathSet_Free() if this function
* returns NFD_OKAY */
/* 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 */
nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath );
/* It is the caller's responsibility to free `outPath` via NFD_FreePathN() if this function returns
* NFD_OKAY */
/* If filterCount is zero, filterList is ignored (you can use NULL) */
/* 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 */
nfdresult_t NFD_PickFolder( const nfdchar_t *defaultPath,
nfdchar_t **outPath);
/* It is the caller's responsibility to free `outPath` via NFD_FreePathN() if this function returns
* 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);
/* Free the 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 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 */
void NFD_PathSet_Free( nfdpathset_t *pathSet );
/* 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
}
#endif
#endif // __cplusplus
#endif
#endif // _NFD_H

View File

@ -0,0 +1,311 @@
/*
Native File Dialog Extended
Repository: https://github.com/btzy/nativefiledialog-extended
License: Zlib
Author: Bernard Teo
This header is a thin C++ wrapper for nfd.h.
C++ projects can choose to use this header instead of nfd.h directly.
Refer to documentation on nfd.h for instructions on how to use these functions.
*/
#ifndef _NFD_HPP
#define _NFD_HPP
#include <nfd.h>
#include <cstddef> // for std::size_t
#include <memory> // for std::unique_ptr
#ifdef NFD_THROWS_EXCEPTIONS
#include <stdexcept>
#endif
namespace NFD {
inline nfdresult_t Init() noexcept {
return ::NFD_Init();
}
inline void Quit() noexcept {
::NFD_Quit();
}
inline void FreePath(nfdnchar_t* outPath) noexcept {
::NFD_FreePathN(outPath);
}
inline nfdresult_t OpenDialog(nfdnchar_t*& outPath,
const nfdnfilteritem_t* filterList = nullptr,
nfdfiltersize_t filterCount = 0,
const nfdnchar_t* defaultPath = nullptr) noexcept {
return ::NFD_OpenDialogN(&outPath, filterList, filterCount, defaultPath);
}
inline nfdresult_t OpenDialogMultiple(const nfdpathset_t*& outPaths,
const nfdnfilteritem_t* filterList = nullptr,
nfdfiltersize_t filterCount = 0,
const nfdnchar_t* defaultPath = nullptr) noexcept {
return ::NFD_OpenDialogMultipleN(&outPaths, filterList, filterCount, defaultPath);
}
inline nfdresult_t SaveDialog(nfdnchar_t*& outPath,
const nfdnfilteritem_t* filterList = nullptr,
nfdfiltersize_t filterCount = 0,
const nfdnchar_t* defaultPath = nullptr,
const nfdnchar_t* defaultName = nullptr) noexcept {
return ::NFD_SaveDialogN(&outPath, filterList, filterCount, defaultPath, defaultName);
}
inline nfdresult_t PickFolder(nfdnchar_t*& outPath,
const nfdnchar_t* defaultPath = nullptr) noexcept {
return ::NFD_PickFolderN(&outPath, defaultPath);
}
inline const char* GetError() noexcept {
return ::NFD_GetError();
}
inline void ClearError() noexcept {
::NFD_ClearError();
}
namespace PathSet {
inline nfdresult_t Count(const nfdpathset_t* pathSet, nfdpathsetsize_t& count) noexcept {
return ::NFD_PathSet_GetCount(pathSet, &count);
}
inline nfdresult_t GetPath(const nfdpathset_t* pathSet,
nfdpathsetsize_t index,
nfdnchar_t*& outPath) noexcept {
return ::NFD_PathSet_GetPathN(pathSet, index, &outPath);
}
inline void FreePath(nfdnchar_t* filePath) noexcept {
::NFD_PathSet_FreePathN(filePath);
}
inline void Free(const nfdpathset_t* pathSet) noexcept {
::NFD_PathSet_Free(pathSet);
}
} // namespace PathSet
#ifdef NFD_DIFFERENT_NATIVE_FUNCTIONS
/* we need the C++ bindings for the UTF-8 functions as well, because there are different functions
* for them */
inline void FreePath(nfdu8char_t* outPath) noexcept {
::NFD_FreePathU8(outPath);
}
inline nfdresult_t OpenDialog(nfdu8char_t*& outPath,
const nfdu8filteritem_t* filterList = nullptr,
nfdfiltersize_t count = 0,
const nfdu8char_t* defaultPath = nullptr) noexcept {
return ::NFD_OpenDialogU8(&outPath, filterList, count, defaultPath);
}
inline nfdresult_t OpenDialogMultiple(const nfdpathset_t*& outPaths,
const nfdu8filteritem_t* filterList = nullptr,
nfdfiltersize_t count = 0,
const nfdu8char_t* defaultPath = nullptr) noexcept {
return ::NFD_OpenDialogMultipleU8(&outPaths, filterList, count, defaultPath);
}
inline nfdresult_t SaveDialog(nfdu8char_t*& outPath,
const nfdu8filteritem_t* filterList = nullptr,
nfdfiltersize_t count = 0,
const nfdu8char_t* defaultPath = nullptr,
const nfdu8char_t* defaultName = nullptr) noexcept {
return ::NFD_SaveDialogU8(&outPath, filterList, count, defaultPath, defaultName);
}
inline nfdresult_t PickFolder(nfdu8char_t*& outPath,
const nfdu8char_t* defaultPath = nullptr) noexcept {
return ::NFD_PickFolderU8(&outPath, defaultPath);
}
namespace PathSet {
inline nfdresult_t GetPath(const nfdpathset_t* pathSet,
nfdpathsetsize_t index,
nfdu8char_t*& outPath) noexcept {
return ::NFD_PathSet_GetPathU8(pathSet, index, &outPath);
}
inline void FreePath(nfdu8char_t* filePath) noexcept {
::NFD_PathSet_FreePathU8(filePath);
}
} // namespace PathSet
#endif
// smart objects
class Guard {
public:
#ifndef NFD_THROWS_EXCEPTIONS
inline Guard() noexcept {
Init(); // always assume that initialization succeeds
}
#else
inline Guard() {
if (!Init()) {
throw std::runtime_error(GetError());
}
}
#endif
inline ~Guard() noexcept { Quit(); }
// Not allowed to copy or move this class
Guard(const Guard&) = delete;
Guard& operator=(const Guard&) = delete;
};
template <typename T>
struct PathDeleter {
inline void operator()(T* ptr) const noexcept { FreePath(ptr); }
};
typedef std::unique_ptr<nfdchar_t, PathDeleter<nfdchar_t>> UniquePath;
typedef std::unique_ptr<nfdnchar_t, PathDeleter<nfdnchar_t>> UniquePathN;
typedef std::unique_ptr<nfdu8char_t, PathDeleter<nfdu8char_t>> UniquePathU8;
struct PathSetDeleter {
inline void operator()(const nfdpathset_t* ptr) const noexcept { PathSet::Free(ptr); }
};
typedef std::unique_ptr<const nfdpathset_t, PathSetDeleter> UniquePathSet;
template <typename T>
struct PathSetPathDeleter {
inline void operator()(T* ptr) const noexcept { PathSet::FreePath(ptr); }
};
typedef std::unique_ptr<nfdchar_t, PathSetPathDeleter<nfdchar_t>> UniquePathSetPath;
typedef std::unique_ptr<nfdnchar_t, PathSetPathDeleter<nfdnchar_t>> UniquePathSetPathN;
typedef std::unique_ptr<nfdu8char_t, PathSetPathDeleter<nfdu8char_t>> UniquePathSetPathU8;
inline nfdresult_t OpenDialog(UniquePathN& outPath,
const nfdnfilteritem_t* filterList = nullptr,
nfdfiltersize_t filterCount = 0,
const nfdnchar_t* defaultPath = nullptr) noexcept {
nfdnchar_t* out;
nfdresult_t res = OpenDialog(out, filterList, filterCount, defaultPath);
if (res == NFD_OKAY) {
outPath.reset(out);
}
return res;
}
inline nfdresult_t OpenDialogMultiple(UniquePathSet& outPaths,
const nfdnfilteritem_t* filterList = nullptr,
nfdfiltersize_t filterCount = 0,
const nfdnchar_t* defaultPath = nullptr) noexcept {
const nfdpathset_t* out;
nfdresult_t res = OpenDialogMultiple(out, filterList, filterCount, defaultPath);
if (res == NFD_OKAY) {
outPaths.reset(out);
}
return res;
}
inline nfdresult_t SaveDialog(UniquePathN& outPath,
const nfdnfilteritem_t* filterList = nullptr,
nfdfiltersize_t filterCount = 0,
const nfdnchar_t* defaultPath = nullptr,
const nfdnchar_t* defaultName = nullptr) noexcept {
nfdnchar_t* out;
nfdresult_t res = SaveDialog(out, filterList, filterCount, defaultPath, defaultName);
if (res == NFD_OKAY) {
outPath.reset(out);
}
return res;
}
inline nfdresult_t PickFolder(UniquePathN& outPath,
const nfdnchar_t* defaultPath = nullptr) noexcept {
nfdnchar_t* out;
nfdresult_t res = PickFolder(out, defaultPath);
if (res == NFD_OKAY) {
outPath.reset(out);
}
return res;
}
#ifdef NFD_DIFFERENT_NATIVE_FUNCTIONS
inline nfdresult_t OpenDialog(UniquePathU8& outPath,
const nfdu8filteritem_t* filterList = nullptr,
nfdfiltersize_t filterCount = 0,
const nfdu8char_t* defaultPath = nullptr) noexcept {
nfdu8char_t* out;
nfdresult_t res = OpenDialog(out, filterList, filterCount, defaultPath);
if (res == NFD_OKAY) {
outPath.reset(out);
}
return res;
}
inline nfdresult_t OpenDialogMultiple(UniquePathSet& outPaths,
const nfdu8filteritem_t* filterList = nullptr,
nfdfiltersize_t filterCount = 0,
const nfdu8char_t* defaultPath = nullptr) noexcept {
const nfdpathset_t* out;
nfdresult_t res = OpenDialogMultiple(out, filterList, filterCount, defaultPath);
if (res == NFD_OKAY) {
outPaths.reset(out);
}
return res;
}
inline nfdresult_t SaveDialog(UniquePathU8& outPath,
const nfdu8filteritem_t* filterList = nullptr,
nfdfiltersize_t filterCount = 0,
const nfdu8char_t* defaultPath = nullptr,
const nfdu8char_t* defaultName = nullptr) noexcept {
nfdu8char_t* out;
nfdresult_t res = SaveDialog(out, filterList, filterCount, defaultPath, defaultName);
if (res == NFD_OKAY) {
outPath.reset(out);
}
return res;
}
inline nfdresult_t PickFolder(UniquePathU8& outPath,
const nfdu8char_t* defaultPath = nullptr) noexcept {
nfdu8char_t* out;
nfdresult_t res = PickFolder(out, defaultPath);
if (res == NFD_OKAY) {
outPath.reset(out);
}
return res;
}
#endif
namespace PathSet {
inline nfdresult_t Count(const UniquePathSet& uniquePathSet, nfdpathsetsize_t& count) noexcept {
return Count(uniquePathSet.get(), count);
}
inline nfdresult_t GetPath(const UniquePathSet& uniquePathSet,
nfdpathsetsize_t index,
UniquePathSetPathN& outPath) noexcept {
nfdnchar_t* out;
nfdresult_t res = GetPath(uniquePathSet.get(), index, out);
if (res == NFD_OKAY) {
outPath.reset(out);
}
return res;
}
#ifdef NFD_DIFFERENT_NATIVE_FUNCTIONS
inline nfdresult_t GetPath(const UniquePathSet& uniquePathSet,
nfdpathsetsize_t index,
UniquePathSetPathU8& outPath) noexcept {
nfdu8char_t* out;
nfdresult_t res = GetPath(uniquePathSet.get(), index, out);
if (res == NFD_OKAY) {
outPath.reset(out);
}
return res;
}
#endif
} // namespace PathSet
} // namespace NFD
#endif

View File

@ -1,290 +1,325 @@
/*
Native File Dialog
http://www.frogtoss.com/labs
Native File Dialog Extended
Repository: https://github.com/btzy/nativefiledialog-extended
License: Zlib
Authors: Bernard Teo, Michael Labbe
*/
#if __has_include(<AppKit/AppKit.h>)
#include <AppKit/AppKit.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
NSMutableArray *buildFilterList = [[NSMutableArray alloc] init];
NSMutableArray* buildFilterList = [[NSMutableArray alloc] init];
char typebuf[NFD_MAX_STRLEN] = {0};
size_t filterListLen = strlen(filterList);
char *p_typebuf = typebuf;
for ( size_t i = 0; i < filterListLen+1; ++i )
{
if ( filterList[i] == ',' || filterList[i] == ';' || filterList[i] == '\0' )
{
if (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;
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;
}
}
// 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];
assert([returnArray count] != 0);
// [buildFilterList release];
return returnArray;
}
static void AddFilterListToDialog( NSSavePanel *dialog, const char *filterList )
{
if ( !filterList || strlen(filterList) == 0 )
return;
static void AddFilterListToDialog(NSSavePanel* dialog,
const nfdnfilteritem_t* filterList,
nfdfiltersize_t filterCount) {
// note: NSOpenPanel inherits from NSSavePanel.
NSArray *allowedFileTypes = BuildAllowedFileTypes( filterList );
if ( [allowedFileTypes count] != 0 )
{
[dialog setAllowedFileTypes:allowedFileTypes];
if (!filterCount) return;
assert(filterList);
// make NSArray of file types
NSArray* allowedFileTypes = BuildAllowedFileTypes(filterList, filterCount);
// set it on the dialog
[dialog setAllowedFileTypes:allowedFileTypes];
}
static void SetDefaultPath(NSSavePanel* dialog, const nfdnchar_t* defaultPath) {
if (!defaultPath || !*defaultPath) return;
NSString* defaultPathString = [NSString stringWithUTF8String:defaultPath];
NSURL* url = [NSURL fileURLWithPath:defaultPathString isDirectory:YES];
[dialog setDirectoryURL:url];
}
static void SetDefaultName(NSSavePanel* dialog, const nfdnchar_t* defaultName) {
if (!defaultName || !*defaultName) return;
NSString* defaultNameString = [NSString stringWithUTF8String:defaultName];
[dialog setNameFieldStringValue:defaultNameString];
}
static nfdresult_t CopyUtf8String(const char* utf8Str, nfdnchar_t** out) {
// byte count, not char count
size_t len = strlen(utf8Str);
// 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;
}
return NFD_ERROR;
}
/* public */
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;
}
/* 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);
// 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_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];
NSOpenPanel* dialog = [NSOpenPanel openPanel];
[dialog setAllowsMultipleSelection:YES];
// Build the filter list
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);
}
}
static void SetDefaultPath( NSSavePanel *dialog, const nfdchar_t *defaultPath )
{
if ( !defaultPath || strlen(defaultPath) == 0 )
return;
NSString *defaultPathString = [NSString stringWithUTF8String: defaultPath];
NSURL *url = [NSURL fileURLWithPath:defaultPathString isDirectory:YES];
[dialog setDirectoryURL:url];
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;
/* fixme: pathset should be pathSet */
static nfdresult_t AllocPathSet( NSArray *urls, nfdpathset_t *pathset )
{
assert(pathset);
assert([urls count]);
pathset->count = (size_t)[urls count];
pathset->indices = NFDi_Malloc( sizeof(size_t)*pathset->count );
if ( !pathset->indices )
{
return NFD_ERROR;
}
// count the total space needed for buf
size_t bufsize = 0;
for ( NSURL *url in urls )
{
NSString *path = [url path];
bufsize += [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
}
pathset->buf = NFDi_Malloc( sizeof(nfdchar_t) * bufsize );
if ( !pathset->buf )
{
return NFD_ERROR;
}
// fill buf
nfdchar_t *p_buf = pathset->buf;
size_t count = 0;
for ( NSURL *url in urls )
{
NSString *path = [url path];
const nfdchar_t *utf8Path = [path UTF8String];
size_t byteLen = [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
memcpy( p_buf, utf8Path, byteLen );
ptrdiff_t index = p_buf - pathset->buf;
assert( index >= 0 );
pathset->indices[count] = (size_t)index;
p_buf += byteLen;
++count;
@autoreleasepool {
// autoreleasepool needed because NSEnumerator uses it
NSEnumerator* enumerator = [urls objectEnumerator];
[enumerator retain];
outEnumerator->ptr = (void*)enumerator;
}
return NFD_OKAY;
}
/* public */
nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath )
{
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;
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;
nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdpathset_t *outPaths )
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
NSOpenPanel *dialog = [NSOpenPanel openPanel];
[dialog setAllowsMultipleSelection:YES];
// 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;
@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;
}
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 )
{
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;
}
#endif // __has_include(<AppKit/AppKit.h>)

View File

@ -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 */
int32_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. */
int32_t character_count = 0;
int32_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');
}

View File

@ -1,39 +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"
#include <stdint.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 );
int32_t NFDi_UTF8_Strlen( const nfdchar_t *str );
int NFDi_IsFilterSegmentChar( char ch );
#ifdef __cplusplus
}
#endif
#endif

View File

@ -1,383 +0,0 @@
/*
Native File Dialog
http://www.frogtoss.com/labs
*/
#if __has_include(<gtk/gtk.h>)
#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 )
{
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 )
{
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;
}
#endif // __has_include(<gtk/gtk.h>)

634
Sources/CNFD/nfd_gtk.cpp Normal file
View File

@ -0,0 +1,634 @@
/*
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!
*/
#if __has_include(<gtk/gtk.h>)
#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);
// 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;
}
#endif // __has_include(<gtk/gtk.h>)

File diff suppressed because it is too large Load Diff

View File

@ -1,307 +0,0 @@
/*
Native File Dialog
http://www.frogtoss.com/labs
*/
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include "nfd.h"
#include "nfd_common.h"
#define SIMPLE_EXEC_IMPLEMENTATION
#include "simple_exec.h"
const char NO_ZENITY_MSG[] = "zenity not installed";
static void AddTypeToFilterName( const char *typebuf, char *filterName, size_t bufsize )
{
size_t len = strlen(filterName);
if( len > 0 )
strncat( filterName, " *.", bufsize - len - 1 );
else
strncat( filterName, "--file-filter=*.", bufsize - len - 1 );
len = strlen(filterName);
strncat( filterName, typebuf, bufsize - len - 1 );
}
static void AddFiltersToCommandArgs(char** commandArgs, int commandArgsLen, const char *filterList )
{
char typebuf[NFD_MAX_STRLEN] = {0};
const char *p_filterList = filterList;
char *p_typebuf = typebuf;
char filterName[NFD_MAX_STRLEN] = {0};
int i;
if ( !filterList || strlen(filterList) == 0 )
return;
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 );
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 */
for(i = 0; commandArgs[i] != NULL && i < commandArgsLen; i++);
commandArgs[i] = strdup(filterName);
filterName[0] = '\0';
if ( *p_filterList == '\0' )
break;
}
if ( !NFDi_IsFilterSegmentChar( *p_filterList ) )
{
*p_typebuf = *p_filterList;
p_typebuf++;
}
p_filterList++;
}
/* always append a wildcard option to the end*/
for(i = 0; commandArgs[i] != NULL && i < commandArgsLen; i++);
commandArgs[i] = strdup("--file-filter=*.*");
}
static nfdresult_t ZenityCommon(char** command, int commandLen, const char* defaultPath, const char* filterList, char** stdOut)
{
if(defaultPath != NULL)
{
char* prefix = "--filename=";
int len = strlen(prefix) + strlen(defaultPath) + 1;
char* tmp = (char*) calloc(len, 1);
strcat(tmp, prefix);
strcat(tmp, defaultPath);
int i;
for(i = 0; command[i] != NULL && i < commandLen; i++);
command[i] = tmp;
}
AddFiltersToCommandArgs(command, commandLen, filterList);
int byteCount = 0;
int exitCode = 0;
int processInvokeError = runCommandArray(stdOut, &byteCount, &exitCode, 0, command);
for(int i = 0; command[i] != NULL && i < commandLen; i++)
free(command[i]);
nfdresult_t result = NFD_OKAY;
if(processInvokeError == COMMAND_NOT_FOUND)
{
NFDi_SetError(NO_ZENITY_MSG);
result = NFD_ERROR;
}
else
{
if(exitCode == 1)
result = NFD_CANCEL;
}
return result;
}
static nfdresult_t AllocPathSet(char* zenityList, nfdpathset_t *pathSet )
{
assert(zenityList);
assert(pathSet);
size_t len = strlen(zenityList) + 1;
pathSet->buf = NFDi_Malloc(len);
int numEntries = 1;
for(size_t i = 0; i < len; i++)
{
char ch = zenityList[i];
if(ch == '|')
{
numEntries++;
ch = '\0';
}
pathSet->buf[i] = ch;
}
pathSet->count = numEntries;
assert( pathSet->count > 0 );
pathSet->indices = NFDi_Malloc( sizeof(size_t)*pathSet->count );
int entry = 0;
pathSet->indices[0] = 0;
for(size_t i = 0; i < len; i++)
{
char ch = zenityList[i];
if(ch == '|')
{
entry++;
pathSet->indices[entry] = i + 1;
}
}
return NFD_OKAY;
}
/* public */
nfdresult_t NFD_OpenDialog( const char *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath )
{
int commandLen = 100;
char* command[commandLen];
memset(command, 0, commandLen * sizeof(char*));
command[0] = strdup("zenity");
command[1] = strdup("--file-selection");
command[2] = strdup("--title=Open File");
char* stdOut = NULL;
nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, filterList, &stdOut);
if(stdOut != NULL)
{
size_t len = strlen(stdOut);
*outPath = NFDi_Malloc(len);
memcpy(*outPath, stdOut, len);
(*outPath)[len-1] = '\0'; // trim out the final \n with a null terminator
free(stdOut);
}
else
{
*outPath = NULL;
}
return result;
}
nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdpathset_t *outPaths )
{
int commandLen = 100;
char* command[commandLen];
memset(command, 0, commandLen * sizeof(char*));
command[0] = strdup("zenity");
command[1] = strdup("--file-selection");
command[2] = strdup("--title=Open Files");
command[3] = strdup("--multiple");
char* stdOut = NULL;
nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, filterList, &stdOut);
if(stdOut != NULL)
{
size_t len = strlen(stdOut);
stdOut[len-1] = '\0'; // remove trailing newline
if ( AllocPathSet( stdOut, outPaths ) == NFD_ERROR )
result = NFD_ERROR;
free(stdOut);
}
else
{
result = NFD_ERROR;
}
return result;
}
nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath )
{
int commandLen = 100;
char* command[commandLen];
memset(command, 0, commandLen * sizeof(char*));
command[0] = strdup("zenity");
command[1] = strdup("--file-selection");
command[2] = strdup("--title=Save File");
command[3] = strdup("--save");
char* stdOut = NULL;
nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, filterList, &stdOut);
if(stdOut != NULL)
{
size_t len = strlen(stdOut);
*outPath = NFDi_Malloc(len);
memcpy(*outPath, stdOut, len);
(*outPath)[len-1] = '\0'; // trim out the final \n with a null terminator
free(stdOut);
}
else
{
*outPath = NULL;
}
return result;
}
nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath,
nfdchar_t **outPath)
{
int commandLen = 100;
char* command[commandLen];
memset(command, 0, commandLen * sizeof(char*));
command[0] = strdup("zenity");
command[1] = strdup("--file-selection");
command[2] = strdup("--directory");
command[3] = strdup("--title=Select folder");
char* stdOut = NULL;
nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, "", &stdOut);
if(stdOut != NULL)
{
size_t len = strlen(stdOut);
*outPath = NFDi_Malloc(len);
memcpy(*outPath, stdOut, len);
(*outPath)[len-1] = '\0'; // trim out the final \n with a null terminator
free(stdOut);
}
else
{
*outPath = NULL;
}
return result;
}

View File

@ -1,218 +0,0 @@
// copied from: https://github.com/wheybags/simple_exec/blob/5a74c507c4ce1b2bb166177ead4cca7cfa23cb35/simple_exec.h
// simple_exec.h, single header library to run external programs + retrieve their status code and output (unix only for now)
//
// do this:
// #define SIMPLE_EXEC_IMPLEMENTATION
// before you include this file in *one* C or C++ file to create the implementation.
// i.e. it should look like this:
// #define SIMPLE_EXEC_IMPLEMENTATION
// #include "simple_exec.h"
#ifndef SIMPLE_EXEC_H
#define SIMPLE_EXEC_H
int runCommand(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* command, ...);
int runCommandArray(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* const* allArgs);
#endif // SIMPLE_EXEC_H
#ifdef SIMPLE_EXEC_IMPLEMENTATION
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <sys/wait.h>
#include <stdarg.h>
#include <fcntl.h>
#define release_assert(exp) { if (!(exp)) { abort(); } }
enum PIPE_FILE_DESCRIPTORS
{
READ_FD = 0,
WRITE_FD = 1
};
enum RUN_COMMAND_ERROR
{
COMMAND_RAN_OK = 0,
COMMAND_NOT_FOUND = 1
};
int runCommandArray(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* const* allArgs)
{
// adapted from: https://stackoverflow.com/a/479103
int bufferSize = 256;
char buffer[bufferSize + 1];
int dataReadFromChildDefaultSize = bufferSize * 5;
int dataReadFromChildSize = dataReadFromChildDefaultSize;
int dataReadFromChildUsed = 0;
char* dataReadFromChild = (char*)malloc(dataReadFromChildSize);
int parentToChild[2];
release_assert(pipe(parentToChild) == 0);
int childToParent[2];
release_assert(pipe(childToParent) == 0);
int errPipe[2];
release_assert(pipe(errPipe) == 0);
pid_t pid;
switch( pid = fork() )
{
case -1:
{
release_assert(0 && "Fork failed");
break;
}
case 0: // child
{
release_assert(dup2(parentToChild[READ_FD ], STDIN_FILENO ) != -1);
release_assert(dup2(childToParent[WRITE_FD], STDOUT_FILENO) != -1);
if(includeStdErr)
{
release_assert(dup2(childToParent[WRITE_FD], STDERR_FILENO) != -1);
}
else
{
int devNull = open("/dev/null", O_WRONLY);
release_assert(dup2(devNull, STDERR_FILENO) != -1);
}
// unused
release_assert(close(parentToChild[WRITE_FD]) == 0);
release_assert(close(childToParent[READ_FD ]) == 0);
release_assert(close(errPipe[READ_FD]) == 0);
const char* command = allArgs[0];
execvp(command, allArgs);
char err = 1;
ssize_t result = write(errPipe[WRITE_FD], &err, 1);
release_assert(result != -1);
close(errPipe[WRITE_FD]);
close(parentToChild[READ_FD]);
close(childToParent[WRITE_FD]);
exit(0);
}
default: // parent
{
// unused
release_assert(close(parentToChild[READ_FD]) == 0);
release_assert(close(childToParent[WRITE_FD]) == 0);
release_assert(close(errPipe[WRITE_FD]) == 0);
while(1)
{
ssize_t bytesRead = 0;
switch(bytesRead = read(childToParent[READ_FD], buffer, bufferSize))
{
case 0: // End-of-File, or non-blocking read.
{
int status = 0;
release_assert(waitpid(pid, &status, 0) == pid);
// done with these now
release_assert(close(parentToChild[WRITE_FD]) == 0);
release_assert(close(childToParent[READ_FD]) == 0);
char errChar = 0;
ssize_t result = read(errPipe[READ_FD], &errChar, 1);
release_assert(result != -1);
close(errPipe[READ_FD]);
if(errChar)
{
free(dataReadFromChild);
return COMMAND_NOT_FOUND;
}
// free any un-needed memory with realloc + add a null terminator for convenience
dataReadFromChild = (char*)realloc(dataReadFromChild, dataReadFromChildUsed + 1);
dataReadFromChild[dataReadFromChildUsed] = '\0';
if(stdOut != NULL)
*stdOut = dataReadFromChild;
else
free(dataReadFromChild);
if(stdOutByteCount != NULL)
*stdOutByteCount = dataReadFromChildUsed;
if(returnCode != NULL)
*returnCode = WEXITSTATUS(status);
return COMMAND_RAN_OK;
}
case -1:
{
release_assert(0 && "read() failed");
break;
}
default:
{
if(dataReadFromChildUsed + bytesRead + 1 >= dataReadFromChildSize)
{
dataReadFromChildSize += dataReadFromChildDefaultSize;
dataReadFromChild = (char*)realloc(dataReadFromChild, dataReadFromChildSize);
}
memcpy(dataReadFromChild + dataReadFromChildUsed, buffer, bytesRead);
dataReadFromChildUsed += bytesRead;
break;
}
}
}
}
}
}
int runCommand(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* command, ...)
{
va_list vl;
va_start(vl, command);
char* currArg = NULL;
int allArgsInitialSize = 16;
int allArgsSize = allArgsInitialSize;
char** allArgs = (char**)malloc(sizeof(char*) * allArgsSize);
allArgs[0] = command;
int i = 1;
do
{
currArg = va_arg(vl, char*);
allArgs[i] = currArg;
i++;
if(i >= allArgsSize)
{
allArgsSize += allArgsInitialSize;
allArgs = (char**)realloc(allArgs, sizeof(char*) * allArgsSize);
}
} while(currArg != NULL);
va_end(vl);
int retval = runCommandArray(stdOut, stdOutByteCount, returnCode, includeStdErr, allArgs);
free(allArgs);
return retval;
}
#endif //SIMPLE_EXEC_IMPLEMENTATION