mirror of
https://github.com/wolfpld/tracy
synced 2026-01-19 04:52:09 +00:00
Update nfd-extended to 1.2.1, retrieve via CPM.
This commit is contained in:
@@ -121,7 +121,6 @@ add_library(TracyDtl INTERFACE)
|
||||
target_sources(TracyDtl INTERFACE ${DTL_HEADERS})
|
||||
target_include_directories(TracyDtl INTERFACE ${DTL_DIR})
|
||||
|
||||
|
||||
# Get Opt
|
||||
|
||||
set(GETOPT_DIR "${ROOT_DIR}/getopt")
|
||||
@@ -130,7 +129,6 @@ set(GETOPT_HEADERS ${GETOPT_DIR}/getopt.h)
|
||||
add_library(TracyGetOpt STATIC EXCLUDE_FROM_ALL ${GETOPT_SOURCES} ${GETOPT_HEADERS})
|
||||
target_include_directories(TracyGetOpt PUBLIC ${GETOPT_DIR})
|
||||
|
||||
|
||||
# ImGui
|
||||
|
||||
CPMAddPackage(
|
||||
@@ -166,50 +164,21 @@ endif()
|
||||
|
||||
# NFD
|
||||
|
||||
if (NOT NO_FILESELECTOR AND NOT EMSCRIPTEN)
|
||||
set(NFD_DIR "${ROOT_DIR}/nfd")
|
||||
|
||||
if (WIN32)
|
||||
set(NFD_SOURCES "${NFD_DIR}/nfd_win.cpp")
|
||||
elseif (APPLE)
|
||||
set(NFD_SOURCES "${NFD_DIR}/nfd_cocoa.m")
|
||||
if(NOT NO_FILESELECTOR AND NOT EMSCRIPTEN)
|
||||
if(GTK_FILESELECTOR)
|
||||
set(NFD_PORTAL OFF)
|
||||
else()
|
||||
if (GTK_FILESELECTOR)
|
||||
set(NFD_SOURCES "${NFD_DIR}/nfd_gtk.cpp")
|
||||
else()
|
||||
set(NFD_SOURCES "${NFD_DIR}/nfd_portal.cpp")
|
||||
endif()
|
||||
set(NFD_PORTAL ON)
|
||||
endif()
|
||||
|
||||
file(GLOB_RECURSE NFD_HEADERS CONFIGURE_DEPENDS RELATIVE ${NFD_DIR} "*.h")
|
||||
add_library(TracyNfd STATIC EXCLUDE_FROM_ALL ${NFD_SOURCES} ${NFD_HEADERS})
|
||||
target_include_directories(TracyNfd PUBLIC ${NFD_DIR})
|
||||
|
||||
if (APPLE)
|
||||
find_library(APPKIT_LIBRARY AppKit)
|
||||
find_library(UNIFORMTYPEIDENTIFIERS_LIBRARY UniformTypeIdentifiers)
|
||||
target_link_libraries(TracyNfd PUBLIC ${APPKIT_LIBRARY} ${UNIFORMTYPEIDENTIFIERS_LIBRARY})
|
||||
elseif (UNIX)
|
||||
if (GTK_FILESELECTOR)
|
||||
pkg_check_modules(GTK3 gtk+-3.0)
|
||||
if (NOT GTK3_FOUND)
|
||||
message(FATAL_ERROR "GTK3 not found. Please install it or set GTK_FILESELECTOR to OFF.")
|
||||
endif()
|
||||
add_library(TracyGtk3 INTERFACE)
|
||||
target_include_directories(TracyGtk3 INTERFACE ${GTK3_INCLUDE_DIRS})
|
||||
target_link_libraries(TracyGtk3 INTERFACE ${GTK3_LINK_LIBRARIES})
|
||||
target_link_libraries(TracyNfd PUBLIC TracyGtk3)
|
||||
else()
|
||||
pkg_check_modules(DBUS dbus-1)
|
||||
if (NOT DBUS_FOUND)
|
||||
message(FATAL_ERROR "D-Bus not found. Please install it or set GTK_FILESELECTOR to ON.")
|
||||
endif()
|
||||
add_library(TracyDbus INTERFACE)
|
||||
target_include_directories(TracyDbus INTERFACE ${DBUS_INCLUDE_DIRS})
|
||||
target_link_libraries(TracyDbus INTERFACE ${DBUS_LINK_LIBRARIES})
|
||||
target_link_libraries(TracyNfd PUBLIC TracyDbus)
|
||||
endif()
|
||||
endif()
|
||||
CPMAddPackage(
|
||||
NAME nfd
|
||||
GITHUB_REPOSITORY btzy/nativefiledialog-extended
|
||||
GIT_TAG v1.2.1
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
OPTIONS
|
||||
"NFD_PORTAL ${NFD_PORTAL}"
|
||||
)
|
||||
endif()
|
||||
|
||||
# PPQSort
|
||||
|
||||
16
nfd/LICENSE
16
nfd/LICENSE
@@ -1,16 +0,0 @@
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
299
nfd/nfd.h
299
nfd/nfd.h
@@ -1,299 +0,0 @@
|
||||
/*
|
||||
Native File Dialog Extended
|
||||
Repository: https://github.com/btzy/nativefiledialog-extended
|
||||
License: Zlib
|
||||
Authors: Bernard Teo, Michael Labbe
|
||||
|
||||
This header contains the functions that can be called by user code.
|
||||
*/
|
||||
|
||||
#ifndef _NFD_H
|
||||
#define _NFD_H
|
||||
|
||||
#if defined(_WIN32)
|
||||
#if defined(NFD_EXPORT)
|
||||
#define NFD_API __declspec(dllexport)
|
||||
#elif defined(NFD_SHARED)
|
||||
#define NFD_API __declspec(dllimport)
|
||||
#endif
|
||||
#else
|
||||
#if defined(NFD_EXPORT) || defined(NFD_SHARED)
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define NFD_API __attribute__((visibility("default")))
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#ifndef NFD_API
|
||||
#define NFD_API
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif // __cplusplus
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
/* denotes UTF-16 char */
|
||||
typedef wchar_t nfdnchar_t;
|
||||
#else
|
||||
/* denotes UTF-8 char */
|
||||
typedef char nfdnchar_t;
|
||||
#endif // _WIN32
|
||||
|
||||
/* opaque data structure -- see NFD_PathSet_* */
|
||||
typedef void nfdpathset_t;
|
||||
#ifndef NFD_PORTAL
|
||||
typedef struct {
|
||||
void* ptr;
|
||||
} nfdpathsetenum_t;
|
||||
#else
|
||||
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 {
|
||||
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;
|
||||
|
||||
/* free a file path that was returned by the dialogs */
|
||||
/* Note: use NFD_PathSet_FreePath to free path from pathset instead of this function */
|
||||
NFD_API 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 */
|
||||
NFD_API nfdresult_t NFD_Init(void);
|
||||
|
||||
/* call this to de-initialize NFD, if NFD_Init returned NFD_OKAY */
|
||||
NFD_API 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 */
|
||||
NFD_API 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 */
|
||||
NFD_API nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths,
|
||||
const nfdnfilteritem_t* filterList,
|
||||
nfdfiltersize_t filterCount,
|
||||
const nfdnchar_t* defaultPath);
|
||||
|
||||
/* save 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 */
|
||||
NFD_API 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 */
|
||||
/* 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 */
|
||||
NFD_API nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath);
|
||||
|
||||
/* 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. */
|
||||
NFD_API const char* NFD_GetError(void);
|
||||
/* clear the error */
|
||||
NFD_API 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 */
|
||||
NFD_API 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 */
|
||||
NFD_API 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
|
||||
NFD_API 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. */
|
||||
NFD_API nfdresult_t NFD_PathSet_GetEnum(const nfdpathset_t* pathSet,
|
||||
nfdpathsetenum_t* outEnumerator);
|
||||
/* Frees an enumerator of the path set. */
|
||||
NFD_API 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 */
|
||||
NFD_API nfdresult_t NFD_PathSet_EnumNextN(nfdpathsetenum_t* enumerator, nfdnchar_t** outPath);
|
||||
|
||||
/* Free the pathSet */
|
||||
NFD_API 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 */
|
||||
NFD_API 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 */
|
||||
NFD_API 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 */
|
||||
NFD_API 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 */
|
||||
NFD_API 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 */
|
||||
NFD_API 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 */
|
||||
NFD_API 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 */
|
||||
NFD_API 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 // __cplusplus
|
||||
|
||||
#endif // _NFD_H
|
||||
391
nfd/nfd_cocoa.m
391
nfd/nfd_cocoa.m
@@ -1,391 +0,0 @@
|
||||
/*
|
||||
Native File Dialog Extended
|
||||
Repository: https://github.com/btzy/nativefiledialog-extended
|
||||
License: Zlib
|
||||
Authors: Bernard Teo, Michael Labbe
|
||||
*/
|
||||
|
||||
#include <AppKit/AppKit.h>
|
||||
#include <Availability.h>
|
||||
#include "nfd.h"
|
||||
|
||||
// MacOS is deprecating the allowedFileTypes property in favour of allowedContentTypes, so we have
|
||||
// to introduce this breaking change. Define NFD_MACOS_ALLOWEDCONTENTTYPES to 1 to have it set the
|
||||
// allowedContentTypes property of the SavePanel or OpenPanel. Define
|
||||
// NFD_MACOS_ALLOWEDCONTENTTYPES to 0 to have it set the allowedFileTypes property of the SavePanel
|
||||
// or OpenPanel. If NFD_MACOS_ALLOWEDCONTENTTYPES is undefined, then it will set it to 1 if
|
||||
// __MAC_OS_X_VERSION_MIN_REQUIRED >= 11.0, and 0 otherwise.
|
||||
#if !defined(NFD_MACOS_ALLOWEDCONTENTTYPES)
|
||||
#if !defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || !defined(__MAC_11_0) || \
|
||||
__MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_11_0
|
||||
#define NFD_MACOS_ALLOWEDCONTENTTYPES 0
|
||||
#else
|
||||
#define NFD_MACOS_ALLOWEDCONTENTTYPES 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if NFD_MACOS_ALLOWEDCONTENTTYPES == 1
|
||||
#include <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
|
||||
#endif
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
#if NFD_MACOS_ALLOWEDCONTENTTYPES == 1
|
||||
// Returns an NSArray of UTType representing the content types.
|
||||
static NSArray* BuildAllowedContentTypes(const nfdnfilteritem_t* filterList,
|
||||
nfdfiltersize_t filterCount) {
|
||||
NSMutableArray* buildFilterList = [[NSMutableArray alloc] init];
|
||||
|
||||
for (nfdfiltersize_t filterIndex = 0; filterIndex != filterCount; ++filterIndex) {
|
||||
// this is the spec to parse (we don't use the friendly name on OS X)
|
||||
const nfdnchar_t* filterSpec = filterList[filterIndex].spec;
|
||||
|
||||
const nfdnchar_t* p_currentFilterBegin = filterSpec;
|
||||
for (const nfdnchar_t* p_filterSpec = filterSpec; *p_filterSpec; ++p_filterSpec) {
|
||||
if (*p_filterSpec == ',') {
|
||||
// add the extension to the array
|
||||
NSString* filterStr = [[NSString alloc]
|
||||
initWithBytes:(const void*)p_currentFilterBegin
|
||||
length:(sizeof(nfdnchar_t) * (p_filterSpec - p_currentFilterBegin))
|
||||
encoding:NSUTF8StringEncoding];
|
||||
UTType* filterType = [UTType typeWithFilenameExtension:filterStr
|
||||
conformingToType:UTTypeData];
|
||||
[filterStr release];
|
||||
if (filterType) [buildFilterList addObject:filterType];
|
||||
p_currentFilterBegin = p_filterSpec + 1;
|
||||
}
|
||||
}
|
||||
// add the extension to the array
|
||||
NSString* filterStr = [[NSString alloc] initWithUTF8String:p_currentFilterBegin];
|
||||
UTType* filterType = [UTType typeWithFilenameExtension:filterStr
|
||||
conformingToType:UTTypeData];
|
||||
[filterStr release];
|
||||
if (filterType) [buildFilterList addObject:filterType];
|
||||
}
|
||||
|
||||
NSArray* returnArray = [NSArray arrayWithArray:buildFilterList];
|
||||
|
||||
[buildFilterList release];
|
||||
|
||||
assert([returnArray count] != 0);
|
||||
|
||||
return returnArray;
|
||||
}
|
||||
#else
|
||||
// Returns an NSArray of NSString representing the file types.
|
||||
static NSArray* BuildAllowedFileTypes(const nfdnfilteritem_t* filterList,
|
||||
nfdfiltersize_t filterCount) {
|
||||
NSMutableArray* buildFilterList = [[NSMutableArray alloc] init];
|
||||
|
||||
for (nfdfiltersize_t filterIndex = 0; filterIndex != filterCount; ++filterIndex) {
|
||||
// this is the spec to parse (we don't use the friendly name on OS X)
|
||||
const nfdnchar_t* filterSpec = filterList[filterIndex].spec;
|
||||
|
||||
const nfdnchar_t* p_currentFilterBegin = filterSpec;
|
||||
for (const nfdnchar_t* p_filterSpec = filterSpec; *p_filterSpec; ++p_filterSpec) {
|
||||
if (*p_filterSpec == ',') {
|
||||
// add the extension to the array
|
||||
NSString* filterStr = [[[NSString alloc]
|
||||
initWithBytes:(const void*)p_currentFilterBegin
|
||||
length:(sizeof(nfdnchar_t) * (p_filterSpec - p_currentFilterBegin))
|
||||
encoding:NSUTF8StringEncoding] autorelease];
|
||||
[buildFilterList addObject:filterStr];
|
||||
p_currentFilterBegin = p_filterSpec + 1;
|
||||
}
|
||||
}
|
||||
// add the extension to the array
|
||||
NSString* filterStr = [NSString stringWithUTF8String:p_currentFilterBegin];
|
||||
[buildFilterList addObject:filterStr];
|
||||
}
|
||||
|
||||
NSArray* returnArray = [NSArray arrayWithArray:buildFilterList];
|
||||
|
||||
[buildFilterList release];
|
||||
|
||||
assert([returnArray count] != 0);
|
||||
|
||||
return returnArray;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void AddFilterListToDialog(NSSavePanel* dialog,
|
||||
const nfdnfilteritem_t* filterList,
|
||||
nfdfiltersize_t filterCount) {
|
||||
// note: NSOpenPanel inherits from NSSavePanel.
|
||||
|
||||
if (!filterCount) return;
|
||||
|
||||
assert(filterList);
|
||||
|
||||
// Make NSArray of file types and set it on the dialog
|
||||
// We use setAllowedFileTypes or setAllowedContentTypes depending on the deployment target
|
||||
#if NFD_MACOS_ALLOWEDCONTENTTYPES == 1
|
||||
NSArray* allowedContentTypes = BuildAllowedContentTypes(filterList, filterCount);
|
||||
[dialog setAllowedContentTypes:allowedContentTypes];
|
||||
#else
|
||||
NSArray* allowedFileTypes = BuildAllowedFileTypes(filterList, filterCount);
|
||||
[dialog setAllowedFileTypes:allowedFileTypes];
|
||||
#endif
|
||||
}
|
||||
|
||||
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_ClearError(void) {
|
||||
NFDi_SetError(NULL);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void NFD_PathSet_FreeEnum(nfdpathsetenum_t* enumerator) {
|
||||
NSEnumerator* real_enum = (NSEnumerator*)enumerator->ptr;
|
||||
[real_enum release];
|
||||
}
|
||||
|
||||
nfdresult_t NFD_PathSet_EnumNextN(nfdpathsetenum_t* enumerator, nfdnchar_t** outPath) {
|
||||
NSEnumerator* real_enum = (NSEnumerator*)enumerator->ptr;
|
||||
|
||||
@autoreleasepool {
|
||||
// autoreleasepool needed because NSURL uses it
|
||||
const NSURL* url = [real_enum nextObject];
|
||||
if (url) {
|
||||
const char* utf8Path = [[url path] UTF8String];
|
||||
return CopyUtf8String(utf8Path, outPath);
|
||||
} else {
|
||||
*outPath = NULL;
|
||||
return NFD_OKAY;
|
||||
}
|
||||
}
|
||||
}
|
||||
631
nfd/nfd_gtk.cpp
631
nfd/nfd_gtk.cpp
@@ -1,631 +0,0 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
1575
nfd/nfd_portal.cpp
1575
nfd/nfd_portal.cpp
File diff suppressed because it is too large
Load Diff
969
nfd/nfd_win.cpp
969
nfd/nfd_win.cpp
@@ -1,969 +0,0 @@
|
||||
/*
|
||||
Native File Dialog Extended
|
||||
Repository: https://github.com/btzy/nativefiledialog-extended
|
||||
License: Zlib
|
||||
Author: Bernard Teo
|
||||
*/
|
||||
|
||||
/* only locally define UNICODE in this compilation unit */
|
||||
#ifndef UNICODE
|
||||
#define UNICODE
|
||||
#endif
|
||||
|
||||
#ifdef __MINGW32__
|
||||
// Explicitly setting NTDDI version, this is necessary for the MinGW compiler
|
||||
#define NTDDI_VERSION NTDDI_VISTA
|
||||
#define _WIN32_WINNT _WIN32_WINNT_VISTA
|
||||
#endif
|
||||
|
||||
#if _MSC_VER
|
||||
// see
|
||||
// https://developercommunity.visualstudio.com/content/problem/185399/error-c2760-in-combaseapih-with-windows-sdk-81-and.html
|
||||
struct IUnknown; // Workaround for "combaseapi.h(229): error C2187: syntax error: 'identifier' was
|
||||
// unexpected here" when using /permissive-
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <shobjidl.h>
|
||||
#include <stdio.h>
|
||||
#include <wchar.h>
|
||||
#include <windows.h>
|
||||
#include "nfd.h"
|
||||
|
||||
namespace {
|
||||
|
||||
/* 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));
|
||||
}
|
||||
|
||||
/* guard objects */
|
||||
template <typename T>
|
||||
struct Release_Guard {
|
||||
T* data;
|
||||
Release_Guard(T* releasable) noexcept : data(releasable) {}
|
||||
~Release_Guard() { data->Release(); }
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
/* helper functions */
|
||||
nfdresult_t AddFiltersToDialog(::IFileDialog* fileOpenDialog,
|
||||
const nfdnfilteritem_t* filterList,
|
||||
nfdfiltersize_t filterCount) {
|
||||
/* filterCount plus 1 because we hardcode the *.* wildcard after the while loop */
|
||||
COMDLG_FILTERSPEC* specList =
|
||||
NFDi_Malloc<COMDLG_FILTERSPEC>(sizeof(COMDLG_FILTERSPEC) * (filterCount + 1));
|
||||
if (!specList) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
/* ad-hoc RAII object to free memory when destructing */
|
||||
struct COMDLG_FILTERSPEC_Guard {
|
||||
COMDLG_FILTERSPEC* _specList;
|
||||
nfdfiltersize_t index;
|
||||
COMDLG_FILTERSPEC_Guard(COMDLG_FILTERSPEC* specList) noexcept
|
||||
: _specList(specList), index(0) {}
|
||||
~COMDLG_FILTERSPEC_Guard() {
|
||||
for (--index; index != static_cast<nfdfiltersize_t>(-1); --index) {
|
||||
NFDi_Free(const_cast<nfdnchar_t*>(_specList[index].pszSpec));
|
||||
}
|
||||
NFDi_Free(_specList);
|
||||
}
|
||||
};
|
||||
|
||||
COMDLG_FILTERSPEC_Guard specListGuard(specList);
|
||||
|
||||
if (filterCount) {
|
||||
assert(filterList);
|
||||
|
||||
// we have filters to add ... format and add them
|
||||
|
||||
// use the index that comes from the RAII object (instead of making a copy), so the RAII
|
||||
// object will know which memory to free
|
||||
nfdfiltersize_t& index = specListGuard.index;
|
||||
|
||||
for (; index != filterCount; ++index) {
|
||||
// set the friendly name of this filter
|
||||
specList[index].pszName = filterList[index].name;
|
||||
|
||||
// set the specification of this filter...
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// calculate space needed (including the trailing '\0')
|
||||
size_t specSize = sep * 2 + wcslen(filterList[index].spec) + 1;
|
||||
|
||||
// malloc the required memory and populate it
|
||||
nfdnchar_t* specBuf = NFDi_Malloc<nfdnchar_t>(sizeof(nfdnchar_t) * specSize);
|
||||
|
||||
if (!specBuf) {
|
||||
// automatic freeing of memory via COMDLG_FILTERSPEC_Guard
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// convert "png,jpg" to "*.png;*.jpg" as required by Windows ...
|
||||
nfdnchar_t* p_specBuf = specBuf;
|
||||
*p_specBuf++ = L'*';
|
||||
*p_specBuf++ = L'.';
|
||||
for (const nfdnchar_t* p_spec = filterList[index].spec; *p_spec; ++p_spec) {
|
||||
if (*p_spec == L',') {
|
||||
*p_specBuf++ = L';';
|
||||
*p_specBuf++ = L'*';
|
||||
*p_specBuf++ = L'.';
|
||||
} else {
|
||||
*p_specBuf++ = *p_spec;
|
||||
}
|
||||
}
|
||||
*p_specBuf++ = L'\0';
|
||||
|
||||
// assert that we had allocated exactly the correct amount of memory that we used
|
||||
assert(static_cast<size_t>(p_specBuf - specBuf) == specSize);
|
||||
|
||||
// save the buffer to the guard object
|
||||
specList[index].pszSpec = specBuf;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add wildcard */
|
||||
specList[filterCount].pszName = L"All files";
|
||||
specList[filterCount].pszSpec = L"*.*";
|
||||
|
||||
// add the filter to the dialog
|
||||
if (!SUCCEEDED(fileOpenDialog->SetFileTypes(filterCount + 1, specList))) {
|
||||
NFDi_SetError("Failed to set the allowable file types for the drop-down menu.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// automatic freeing of memory via COMDLG_FILTERSPEC_Guard
|
||||
return NFD_OKAY;
|
||||
}
|
||||
|
||||
/* call after AddFiltersToDialog */
|
||||
nfdresult_t SetDefaultExtension(::IFileDialog* fileOpenDialog,
|
||||
const nfdnfilteritem_t* filterList,
|
||||
nfdfiltersize_t filterCount) {
|
||||
// if there are no filters, then don't set default extensions
|
||||
if (!filterCount) {
|
||||
return NFD_OKAY;
|
||||
}
|
||||
|
||||
assert(filterList);
|
||||
|
||||
// set the first item as the default index, and set the default extension
|
||||
if (!SUCCEEDED(fileOpenDialog->SetFileTypeIndex(1))) {
|
||||
NFDi_SetError("Failed to set the selected file type index.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// set the first item as the default file extension
|
||||
const nfdnchar_t* p_spec = filterList[0].spec;
|
||||
for (; *p_spec; ++p_spec) {
|
||||
if (*p_spec == ',') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (*p_spec) {
|
||||
// multiple file extensions for this type (need to allocate memory)
|
||||
size_t numChars = p_spec - filterList[0].spec;
|
||||
// allocate one more char space for the '\0'
|
||||
nfdnchar_t* extnBuf = NFDi_Malloc<nfdnchar_t>(sizeof(nfdnchar_t) * (numChars + 1));
|
||||
if (!extnBuf) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
Free_Guard<nfdnchar_t> extnBufGuard(extnBuf);
|
||||
|
||||
// copy the extension
|
||||
for (size_t i = 0; i != numChars; ++i) {
|
||||
extnBuf[i] = filterList[0].spec[i];
|
||||
}
|
||||
// pad with trailing '\0'
|
||||
extnBuf[numChars] = L'\0';
|
||||
|
||||
if (!SUCCEEDED(fileOpenDialog->SetDefaultExtension(extnBuf))) {
|
||||
NFDi_SetError("Failed to set default extension.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
} else {
|
||||
// single file extension for this type (no need to allocate memory)
|
||||
if (!SUCCEEDED(fileOpenDialog->SetDefaultExtension(filterList[0].spec))) {
|
||||
NFDi_SetError("Failed to set default extension.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return NFD_OKAY;
|
||||
}
|
||||
|
||||
nfdresult_t SetDefaultPath(IFileDialog* dialog, const nfdnchar_t* defaultPath) {
|
||||
if (!defaultPath || !*defaultPath) return NFD_OKAY;
|
||||
|
||||
IShellItem* folder;
|
||||
HRESULT result = SHCreateItemFromParsingName(defaultPath, nullptr, IID_PPV_ARGS(&folder));
|
||||
|
||||
// Valid non results.
|
||||
if (result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) ||
|
||||
result == HRESULT_FROM_WIN32(ERROR_INVALID_DRIVE)) {
|
||||
return NFD_OKAY;
|
||||
}
|
||||
|
||||
if (!SUCCEEDED(result)) {
|
||||
NFDi_SetError("Failed to create ShellItem for setting the default path.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
Release_Guard<IShellItem> folderGuard(folder);
|
||||
|
||||
// SetDefaultFolder() might use another recently used folder if available, so the user doesn't
|
||||
// need to keep navigating back to the default folder (recommended by Windows). change to
|
||||
// SetFolder() if you always want to use the default folder
|
||||
if (!SUCCEEDED(dialog->SetDefaultFolder(folder))) {
|
||||
NFDi_SetError("Failed to set default path.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
return NFD_OKAY;
|
||||
}
|
||||
|
||||
nfdresult_t SetDefaultName(IFileDialog* dialog, const nfdnchar_t* defaultName) {
|
||||
if (!defaultName || !*defaultName) return NFD_OKAY;
|
||||
|
||||
if (!SUCCEEDED(dialog->SetFileName(defaultName))) {
|
||||
NFDi_SetError("Failed to set default file name.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
return NFD_OKAY;
|
||||
}
|
||||
|
||||
nfdresult_t AddOptions(IFileDialog* dialog, FILEOPENDIALOGOPTIONS options) {
|
||||
FILEOPENDIALOGOPTIONS existingOptions;
|
||||
if (!SUCCEEDED(dialog->GetOptions(&existingOptions))) {
|
||||
NFDi_SetError("Failed to get options.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
if (!SUCCEEDED(dialog->SetOptions(existingOptions | options))) {
|
||||
NFDi_SetError("Failed to set options.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
return NFD_OKAY;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
const char* NFD_GetError(void) {
|
||||
return g_errorstr;
|
||||
}
|
||||
|
||||
void NFD_ClearError(void) {
|
||||
NFDi_SetError(nullptr);
|
||||
}
|
||||
|
||||
/* public */
|
||||
|
||||
namespace {
|
||||
// The user might have initialized with COINIT_MULTITHREADED before,
|
||||
// in which case we will fail to do CoInitializeEx(), but file dialogs will still work.
|
||||
// See https://github.com/mlabbe/nativefiledialog/issues/72 for more information.
|
||||
bool needs_uninitialize;
|
||||
} // namespace
|
||||
|
||||
nfdresult_t NFD_Init(void) {
|
||||
// Init COM library.
|
||||
HRESULT result =
|
||||
::CoInitializeEx(nullptr, ::COINIT_APARTMENTTHREADED | ::COINIT_DISABLE_OLE1DDE);
|
||||
|
||||
if (SUCCEEDED(result)) {
|
||||
needs_uninitialize = true;
|
||||
return NFD_OKAY;
|
||||
} else if (result == RPC_E_CHANGED_MODE) {
|
||||
// If this happens, the user already initialized COM using COINIT_MULTITHREADED,
|
||||
// so COM will still work, but we shouldn't uninitialize it later.
|
||||
needs_uninitialize = false;
|
||||
return NFD_OKAY;
|
||||
} else {
|
||||
NFDi_SetError("Failed to initialize COM.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
}
|
||||
void NFD_Quit(void) {
|
||||
if (needs_uninitialize) ::CoUninitialize();
|
||||
}
|
||||
|
||||
void NFD_FreePathN(nfdnchar_t* filePath) {
|
||||
assert(filePath);
|
||||
::CoTaskMemFree(filePath);
|
||||
}
|
||||
|
||||
nfdresult_t NFD_OpenDialogN(nfdnchar_t** outPath,
|
||||
const nfdnfilteritem_t* filterList,
|
||||
nfdfiltersize_t filterCount,
|
||||
const nfdnchar_t* defaultPath) {
|
||||
::IFileOpenDialog* fileOpenDialog;
|
||||
|
||||
// Create dialog
|
||||
HRESULT result = ::CoCreateInstance(::CLSID_FileOpenDialog,
|
||||
nullptr,
|
||||
CLSCTX_ALL,
|
||||
::IID_IFileOpenDialog,
|
||||
reinterpret_cast<void**>(&fileOpenDialog));
|
||||
|
||||
if (!SUCCEEDED(result)) {
|
||||
NFDi_SetError("Could not create dialog.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// make sure we remember to free the dialog
|
||||
Release_Guard<::IFileOpenDialog> fileOpenDialogGuard(fileOpenDialog);
|
||||
|
||||
// Build the filter list
|
||||
if (!AddFiltersToDialog(fileOpenDialog, filterList, filterCount)) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// Set auto-completed default extension
|
||||
if (!SetDefaultExtension(fileOpenDialog, filterList, filterCount)) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// Set the default path
|
||||
if (!SetDefaultPath(fileOpenDialog, defaultPath)) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// Only show file system items
|
||||
if (!AddOptions(fileOpenDialog, ::FOS_FORCEFILESYSTEM)) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// Show the dialog.
|
||||
result = fileOpenDialog->Show(nullptr);
|
||||
if (SUCCEEDED(result)) {
|
||||
// Get the file name
|
||||
::IShellItem* psiResult;
|
||||
result = fileOpenDialog->GetResult(&psiResult);
|
||||
if (!SUCCEEDED(result)) {
|
||||
NFDi_SetError("Could not get shell item from dialog.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
Release_Guard<::IShellItem> psiResultGuard(psiResult);
|
||||
|
||||
nfdnchar_t* filePath;
|
||||
result = psiResult->GetDisplayName(::SIGDN_FILESYSPATH, &filePath);
|
||||
if (!SUCCEEDED(result)) {
|
||||
NFDi_SetError("Could not get file path from shell item returned by dialog.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
*outPath = filePath;
|
||||
|
||||
return NFD_OKAY;
|
||||
} else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED)) {
|
||||
return NFD_CANCEL;
|
||||
} else {
|
||||
NFDi_SetError("File dialog box show failed.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths,
|
||||
const nfdnfilteritem_t* filterList,
|
||||
nfdfiltersize_t filterCount,
|
||||
const nfdnchar_t* defaultPath) {
|
||||
::IFileOpenDialog* fileOpenDialog(nullptr);
|
||||
|
||||
// Create dialog
|
||||
HRESULT result = ::CoCreateInstance(::CLSID_FileOpenDialog,
|
||||
nullptr,
|
||||
CLSCTX_ALL,
|
||||
::IID_IFileOpenDialog,
|
||||
reinterpret_cast<void**>(&fileOpenDialog));
|
||||
|
||||
if (!SUCCEEDED(result)) {
|
||||
NFDi_SetError("Could not create dialog.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// make sure we remember to free the dialog
|
||||
Release_Guard<::IFileOpenDialog> fileOpenDialogGuard(fileOpenDialog);
|
||||
|
||||
// Build the filter list
|
||||
if (!AddFiltersToDialog(fileOpenDialog, filterList, filterCount)) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// Set auto-completed default extension
|
||||
if (!SetDefaultExtension(fileOpenDialog, filterList, filterCount)) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// Set the default path
|
||||
if (!SetDefaultPath(fileOpenDialog, defaultPath)) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// Set a flag for multiple options and file system items only
|
||||
if (!AddOptions(fileOpenDialog, ::FOS_FORCEFILESYSTEM | ::FOS_ALLOWMULTISELECT)) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// Show the dialog.
|
||||
result = fileOpenDialog->Show(nullptr);
|
||||
if (SUCCEEDED(result)) {
|
||||
::IShellItemArray* shellItems;
|
||||
result = fileOpenDialog->GetResults(&shellItems);
|
||||
if (!SUCCEEDED(result)) {
|
||||
NFDi_SetError("Could not get shell items.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// save the path set to the output
|
||||
*outPaths = static_cast<void*>(shellItems);
|
||||
|
||||
return NFD_OKAY;
|
||||
} else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED)) {
|
||||
return NFD_CANCEL;
|
||||
} else {
|
||||
NFDi_SetError("File dialog box show failed.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath,
|
||||
const nfdnfilteritem_t* filterList,
|
||||
nfdfiltersize_t filterCount,
|
||||
const nfdnchar_t* defaultPath,
|
||||
const nfdnchar_t* defaultName) {
|
||||
::IFileSaveDialog* fileSaveDialog;
|
||||
|
||||
// Create dialog
|
||||
HRESULT result = ::CoCreateInstance(::CLSID_FileSaveDialog,
|
||||
nullptr,
|
||||
CLSCTX_ALL,
|
||||
::IID_IFileSaveDialog,
|
||||
reinterpret_cast<void**>(&fileSaveDialog));
|
||||
|
||||
if (!SUCCEEDED(result)) {
|
||||
NFDi_SetError("Could not create dialog.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// make sure we remember to free the dialog
|
||||
Release_Guard<::IFileSaveDialog> fileSaveDialogGuard(fileSaveDialog);
|
||||
|
||||
// Build the filter list
|
||||
if (!AddFiltersToDialog(fileSaveDialog, filterList, filterCount)) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// Set default extension
|
||||
if (!SetDefaultExtension(fileSaveDialog, filterList, filterCount)) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// Set the default path
|
||||
if (!SetDefaultPath(fileSaveDialog, defaultPath)) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// Set the default name
|
||||
if (!SetDefaultName(fileSaveDialog, defaultName)) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// Only show file system items
|
||||
if (!AddOptions(fileSaveDialog, ::FOS_FORCEFILESYSTEM)) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// Show the dialog.
|
||||
result = fileSaveDialog->Show(nullptr);
|
||||
if (SUCCEEDED(result)) {
|
||||
// Get the file name
|
||||
::IShellItem* psiResult;
|
||||
result = fileSaveDialog->GetResult(&psiResult);
|
||||
if (!SUCCEEDED(result)) {
|
||||
NFDi_SetError("Could not get shell item from dialog.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
Release_Guard<::IShellItem> psiResultGuard(psiResult);
|
||||
|
||||
nfdnchar_t* filePath;
|
||||
result = psiResult->GetDisplayName(::SIGDN_FILESYSPATH, &filePath);
|
||||
if (!SUCCEEDED(result)) {
|
||||
NFDi_SetError("Could not get file path from shell item returned by dialog.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
*outPath = filePath;
|
||||
|
||||
return NFD_OKAY;
|
||||
} else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED)) {
|
||||
return NFD_CANCEL;
|
||||
} else {
|
||||
NFDi_SetError("File dialog box show failed.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath) {
|
||||
::IFileOpenDialog* fileOpenDialog;
|
||||
|
||||
// Create dialog
|
||||
if (!SUCCEEDED(::CoCreateInstance(::CLSID_FileOpenDialog,
|
||||
nullptr,
|
||||
CLSCTX_ALL,
|
||||
::IID_IFileOpenDialog,
|
||||
reinterpret_cast<void**>(&fileOpenDialog)))) {
|
||||
NFDi_SetError("Could not create dialog.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
Release_Guard<::IFileOpenDialog> fileOpenDialogGuard(fileOpenDialog);
|
||||
|
||||
// Set the default path
|
||||
if (!SetDefaultPath(fileOpenDialog, defaultPath)) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// Only show items that are folders and on the file system
|
||||
if (!AddOptions(fileOpenDialog, ::FOS_FORCEFILESYSTEM | ::FOS_PICKFOLDERS)) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// Show the dialog to the user
|
||||
const HRESULT result = fileOpenDialog->Show(nullptr);
|
||||
if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED)) {
|
||||
return NFD_CANCEL;
|
||||
} else if (!SUCCEEDED(result)) {
|
||||
NFDi_SetError("File dialog box show failed.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// Get the shell item result
|
||||
::IShellItem* psiResult;
|
||||
if (!SUCCEEDED(fileOpenDialog->GetResult(&psiResult))) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
Release_Guard<::IShellItem> psiResultGuard(psiResult);
|
||||
|
||||
// Finally get the path
|
||||
nfdnchar_t* filePath;
|
||||
// Why are we not using SIGDN_FILESYSPATH?
|
||||
if (!SUCCEEDED(psiResult->GetDisplayName(::SIGDN_DESKTOPABSOLUTEPARSING, &filePath))) {
|
||||
NFDi_SetError("Could not get file path from shell item returned by dialog.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
*outPath = filePath;
|
||||
|
||||
return NFD_OKAY;
|
||||
}
|
||||
|
||||
nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, nfdpathsetsize_t* count) {
|
||||
assert(pathSet);
|
||||
// const_cast because methods on IShellItemArray aren't const, but it should act like const to
|
||||
// the caller
|
||||
::IShellItemArray* psiaPathSet =
|
||||
const_cast<::IShellItemArray*>(static_cast<const ::IShellItemArray*>(pathSet));
|
||||
|
||||
DWORD numPaths;
|
||||
if (!SUCCEEDED(psiaPathSet->GetCount(&numPaths))) {
|
||||
NFDi_SetError("Could not get path count.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
*count = numPaths;
|
||||
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 IShellItemArray aren't const, but it should act like const to
|
||||
// the caller
|
||||
::IShellItemArray* psiaPathSet =
|
||||
const_cast<::IShellItemArray*>(static_cast<const ::IShellItemArray*>(pathSet));
|
||||
|
||||
::IShellItem* psiPath;
|
||||
if (!SUCCEEDED(psiaPathSet->GetItemAt(index, &psiPath))) {
|
||||
NFDi_SetError("Could not get shell item.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
Release_Guard<::IShellItem> psiPathGuard(psiPath);
|
||||
|
||||
nfdnchar_t* name;
|
||||
if (!SUCCEEDED(psiPath->GetDisplayName(::SIGDN_FILESYSPATH, &name))) {
|
||||
NFDi_SetError("Could not get file path from shell item.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
*outPath = name;
|
||||
return NFD_OKAY;
|
||||
}
|
||||
|
||||
nfdresult_t NFD_PathSet_GetEnum(const nfdpathset_t* pathSet, nfdpathsetenum_t* outEnumerator) {
|
||||
assert(pathSet);
|
||||
// const_cast because methods on IShellItemArray aren't const, but it should act like const to
|
||||
// the caller
|
||||
::IShellItemArray* psiaPathSet =
|
||||
const_cast<::IShellItemArray*>(static_cast<const ::IShellItemArray*>(pathSet));
|
||||
|
||||
::IEnumShellItems* pesiPaths;
|
||||
if (!SUCCEEDED(psiaPathSet->EnumItems(&pesiPaths))) {
|
||||
NFDi_SetError("Could not get enumerator.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
outEnumerator->ptr = static_cast<void*>(pesiPaths);
|
||||
return NFD_OKAY;
|
||||
}
|
||||
|
||||
void NFD_PathSet_FreeEnum(nfdpathsetenum_t* enumerator) {
|
||||
assert(enumerator->ptr);
|
||||
|
||||
::IEnumShellItems* pesiPaths = static_cast<::IEnumShellItems*>(enumerator->ptr);
|
||||
|
||||
// free the enumerator memory
|
||||
pesiPaths->Release();
|
||||
}
|
||||
|
||||
nfdresult_t NFD_PathSet_EnumNextN(nfdpathsetenum_t* enumerator, nfdnchar_t** outPath) {
|
||||
assert(enumerator->ptr);
|
||||
|
||||
::IEnumShellItems* pesiPaths = static_cast<::IEnumShellItems*>(enumerator->ptr);
|
||||
|
||||
::IShellItem* psiPath;
|
||||
HRESULT res = pesiPaths->Next(1, &psiPath, NULL);
|
||||
if (!SUCCEEDED(res)) {
|
||||
NFDi_SetError("Could not get next item of enumerator.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
if (res != S_OK) {
|
||||
*outPath = nullptr;
|
||||
return NFD_OKAY;
|
||||
}
|
||||
|
||||
Release_Guard<::IShellItem> psiPathGuard(psiPath);
|
||||
|
||||
nfdnchar_t* name;
|
||||
if (!SUCCEEDED(psiPath->GetDisplayName(::SIGDN_FILESYSPATH, &name))) {
|
||||
NFDi_SetError("Could not get file path from shell item.");
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
*outPath = name;
|
||||
return NFD_OKAY;
|
||||
}
|
||||
|
||||
void NFD_PathSet_Free(const nfdpathset_t* pathSet) {
|
||||
assert(pathSet);
|
||||
// const_cast because methods on IShellItemArray aren't const, but it should act like const to
|
||||
// the caller
|
||||
::IShellItemArray* psiaPathSet =
|
||||
const_cast<::IShellItemArray*>(static_cast<const ::IShellItemArray*>(pathSet));
|
||||
|
||||
// free the path set memory
|
||||
psiaPathSet->Release();
|
||||
}
|
||||
|
||||
namespace {
|
||||
// allocs the space in outStr -- call NFDi_Free()
|
||||
nfdresult_t CopyCharToWChar(const nfdu8char_t* inStr, nfdnchar_t*& outStr) {
|
||||
int charsNeeded = MultiByteToWideChar(CP_UTF8, 0, inStr, -1, nullptr, 0);
|
||||
assert(charsNeeded);
|
||||
|
||||
nfdnchar_t* tmp_outStr = NFDi_Malloc<nfdnchar_t>(sizeof(nfdnchar_t) * charsNeeded);
|
||||
if (!tmp_outStr) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
int ret = MultiByteToWideChar(CP_UTF8, 0, inStr, -1, tmp_outStr, charsNeeded);
|
||||
assert(ret && ret == charsNeeded);
|
||||
(void)ret; // prevent warning in release build
|
||||
outStr = tmp_outStr;
|
||||
return NFD_OKAY;
|
||||
}
|
||||
|
||||
// allocs the space in outPath -- call NFDi_Free()
|
||||
nfdresult_t CopyWCharToNFDChar(const nfdnchar_t* inStr, nfdu8char_t*& outStr) {
|
||||
int bytesNeeded = WideCharToMultiByte(CP_UTF8, 0, inStr, -1, nullptr, 0, nullptr, nullptr);
|
||||
assert(bytesNeeded);
|
||||
|
||||
nfdu8char_t* tmp_outStr = NFDi_Malloc<nfdu8char_t>(sizeof(nfdu8char_t) * bytesNeeded);
|
||||
if (!tmp_outStr) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
int ret = WideCharToMultiByte(CP_UTF8, 0, inStr, -1, tmp_outStr, bytesNeeded, nullptr, nullptr);
|
||||
assert(ret && ret == bytesNeeded);
|
||||
(void)ret; // prevent warning in release build
|
||||
outStr = tmp_outStr;
|
||||
return NFD_OKAY;
|
||||
}
|
||||
|
||||
struct FilterItem_Guard {
|
||||
nfdnfilteritem_t* data;
|
||||
nfdfiltersize_t index;
|
||||
FilterItem_Guard() noexcept : data(nullptr), index(0) {}
|
||||
~FilterItem_Guard() {
|
||||
assert(data || index == 0);
|
||||
for (--index; index != static_cast<nfdfiltersize_t>(-1); --index) {
|
||||
NFDi_Free(const_cast<nfdnchar_t*>(data[index].spec));
|
||||
NFDi_Free(const_cast<nfdnchar_t*>(data[index].name));
|
||||
}
|
||||
if (data) NFDi_Free(data);
|
||||
}
|
||||
};
|
||||
|
||||
nfdresult_t CopyFilterItem(const nfdu8filteritem_t* filterList,
|
||||
nfdfiltersize_t count,
|
||||
FilterItem_Guard& filterItemsNGuard) {
|
||||
if (count) {
|
||||
nfdnfilteritem_t*& filterItemsN = filterItemsNGuard.data;
|
||||
filterItemsN = NFDi_Malloc<nfdnfilteritem_t>(sizeof(nfdnfilteritem_t) * count);
|
||||
if (!filterItemsN) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
nfdfiltersize_t& index = filterItemsNGuard.index;
|
||||
for (; index != count; ++index) {
|
||||
nfdresult_t res = CopyCharToWChar(filterList[index].name,
|
||||
const_cast<nfdnchar_t*&>(filterItemsN[index].name));
|
||||
if (!res) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
res = CopyCharToWChar(filterList[index].spec,
|
||||
const_cast<nfdnchar_t*&>(filterItemsN[index].spec));
|
||||
if (!res) {
|
||||
// remember to free the name, because we also created it (and it won't be protected
|
||||
// by the guard, because we have not incremented the index)
|
||||
NFDi_Free(const_cast<nfdnchar_t*>(filterItemsN[index].name));
|
||||
return NFD_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NFD_OKAY;
|
||||
}
|
||||
nfdresult_t ConvertU8ToNative(const nfdu8char_t* u8Text, FreeCheck_Guard<nfdnchar_t>& nativeText) {
|
||||
if (u8Text) {
|
||||
nfdresult_t res = CopyCharToWChar(u8Text, nativeText.data);
|
||||
if (!res) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
}
|
||||
return NFD_OKAY;
|
||||
}
|
||||
void NormalizePathSeparator(nfdnchar_t* path) {
|
||||
if (path) {
|
||||
for (; *path; ++path) {
|
||||
if (*path == L'/') *path = L'\\';
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void NFD_FreePathU8(nfdu8char_t* outPath) {
|
||||
NFDi_Free(outPath);
|
||||
}
|
||||
|
||||
nfdresult_t NFD_OpenDialogU8(nfdu8char_t** outPath,
|
||||
const nfdu8filteritem_t* filterList,
|
||||
nfdfiltersize_t count,
|
||||
const nfdu8char_t* defaultPath) {
|
||||
// populate the real nfdnfilteritem_t
|
||||
FilterItem_Guard filterItemsNGuard;
|
||||
if (!CopyFilterItem(filterList, count, filterItemsNGuard)) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// convert and normalize the default path, but only if it is not nullptr
|
||||
FreeCheck_Guard<nfdnchar_t> defaultPathNGuard;
|
||||
ConvertU8ToNative(defaultPath, defaultPathNGuard);
|
||||
NormalizePathSeparator(defaultPathNGuard.data);
|
||||
|
||||
// call the native function
|
||||
nfdnchar_t* outPathN;
|
||||
nfdresult_t res =
|
||||
NFD_OpenDialogN(&outPathN, filterItemsNGuard.data, count, defaultPathNGuard.data);
|
||||
|
||||
if (res != NFD_OKAY) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// convert the outPath to UTF-8
|
||||
res = CopyWCharToNFDChar(outPathN, *outPath);
|
||||
|
||||
// free the native out path, and return the result
|
||||
NFD_FreePathN(outPathN);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
// populate the real nfdnfilteritem_t
|
||||
FilterItem_Guard filterItemsNGuard;
|
||||
if (!CopyFilterItem(filterList, count, filterItemsNGuard)) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// convert and normalize the default path, but only if it is not nullptr
|
||||
FreeCheck_Guard<nfdnchar_t> defaultPathNGuard;
|
||||
ConvertU8ToNative(defaultPath, defaultPathNGuard);
|
||||
NormalizePathSeparator(defaultPathNGuard.data);
|
||||
|
||||
// call the native function
|
||||
return NFD_OpenDialogMultipleN(outPaths, filterItemsNGuard.data, count, defaultPathNGuard.data);
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
// populate the real nfdnfilteritem_t
|
||||
FilterItem_Guard filterItemsNGuard;
|
||||
if (!CopyFilterItem(filterList, count, filterItemsNGuard)) {
|
||||
return NFD_ERROR;
|
||||
}
|
||||
|
||||
// convert and normalize the default path, but only if it is not nullptr
|
||||
FreeCheck_Guard<nfdnchar_t> defaultPathNGuard;
|
||||
ConvertU8ToNative(defaultPath, defaultPathNGuard);
|
||||
NormalizePathSeparator(defaultPathNGuard.data);
|
||||
|
||||
// convert the default name, but only if it is not nullptr
|
||||
FreeCheck_Guard<nfdnchar_t> defaultNameNGuard;
|
||||
ConvertU8ToNative(defaultName, defaultNameNGuard);
|
||||
|
||||
// call the native function
|
||||
nfdnchar_t* outPathN;
|
||||
nfdresult_t res = NFD_SaveDialogN(
|
||||
&outPathN, filterItemsNGuard.data, count, defaultPathNGuard.data, defaultNameNGuard.data);
|
||||
|
||||
if (res != NFD_OKAY) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// convert the outPath to UTF-8
|
||||
res = CopyWCharToNFDChar(outPathN, *outPath);
|
||||
|
||||
// free the native out path, and return the result
|
||||
NFD_FreePathN(outPathN);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
// convert and normalize the default path, but only if it is not nullptr
|
||||
FreeCheck_Guard<nfdnchar_t> defaultPathNGuard;
|
||||
ConvertU8ToNative(defaultPath, defaultPathNGuard);
|
||||
NormalizePathSeparator(defaultPathNGuard.data);
|
||||
|
||||
// call the native function
|
||||
nfdnchar_t* outPathN;
|
||||
nfdresult_t res = NFD_PickFolderN(&outPathN, defaultPathNGuard.data);
|
||||
|
||||
if (res != NFD_OKAY) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// convert the outPath to UTF-8
|
||||
res = CopyWCharToNFDChar(outPathN, *outPath);
|
||||
|
||||
// free the native out path, and return the result
|
||||
NFD_FreePathN(outPathN);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
// call the native function
|
||||
nfdnchar_t* outPathN;
|
||||
nfdresult_t res = NFD_PathSet_GetPathN(pathSet, index, &outPathN);
|
||||
|
||||
if (res != NFD_OKAY) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// convert the outPath to UTF-8
|
||||
res = CopyWCharToNFDChar(outPathN, *outPath);
|
||||
|
||||
// free the native out path, and return the result
|
||||
NFD_FreePathN(outPathN);
|
||||
return res;
|
||||
}
|
||||
|
||||
nfdresult_t NFD_PathSet_EnumNextU8(nfdpathsetenum_t* enumerator, nfdu8char_t** outPath) {
|
||||
// call the native function
|
||||
nfdnchar_t* outPathN;
|
||||
nfdresult_t res = NFD_PathSet_EnumNextN(enumerator, &outPathN);
|
||||
|
||||
if (res != NFD_OKAY) {
|
||||
return res;
|
||||
}
|
||||
|
||||
if (outPathN) {
|
||||
// convert the outPath to UTF-8
|
||||
res = CopyWCharToNFDChar(outPathN, *outPath);
|
||||
|
||||
// free the native out path, and return the result
|
||||
NFD_FreePathN(outPathN);
|
||||
} else {
|
||||
*outPath = nullptr;
|
||||
res = NFD_OKAY;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@@ -197,8 +197,10 @@ add_custom_target(git-ref
|
||||
add_dependencies(${PROJECT_NAME} git-ref)
|
||||
|
||||
if(NOT EMSCRIPTEN)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE TracyNfd)
|
||||
if (NOT USE_WAYLAND)
|
||||
if(NOT NO_FILESELECTOR)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE nfd::nfd)
|
||||
endif()
|
||||
if(NOT USE_WAYLAND)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE TracyGlfw3)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# ifdef __EMSCRIPTEN__
|
||||
# include <emscripten.h>
|
||||
# else
|
||||
# include "../nfd/nfd.h"
|
||||
# include <nfd.h>
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
Reference in New Issue
Block a user