diff --git a/src/nfd_common.c b/src/nfd_common.c index 269fbd2..2df2c2a 100644 --- a/src/nfd_common.c +++ b/src/nfd_common.c @@ -50,6 +50,15 @@ void *NFDi_Malloc( size_t bytes ) return ptr; } +void *NFDi_Calloc( size_t num, size_t size ) +{ + void *ptr = calloc(num, size); + if ( !ptr ) + NFDi_SetError("NFDi_Calloc failed."); + + return ptr; +} + void NFDi_Free( void *ptr ) { assert(ptr); diff --git a/src/nfd_common.h b/src/nfd_common.h index a3f6b4a..f4a840b 100644 --- a/src/nfd_common.h +++ b/src/nfd_common.h @@ -23,6 +23,7 @@ extern "C" { void *NFDi_Malloc( size_t bytes ); +void *NFDi_Calloc( size_t num, size_t size ); void NFDi_Free( void *ptr ); void NFDi_SetError( const char *msg ); int NFDi_SafeStrncpy( char *dst, const char *src, size_t maxCopy ); diff --git a/src/nfd_win_legacy.c b/src/nfd_win_legacy.c new file mode 100644 index 0000000..1f7b8ce --- /dev/null +++ b/src/nfd_win_legacy.c @@ -0,0 +1,298 @@ +/* + Native File Dialog + + http://www.frogtoss.com/labs + */ + +#include + +/* only locally define UNICODE in this compilation unit */ +#ifndef UNICODE +#define UNICODE +#endif + +#include +#include +#include +#include +#include +#include "nfd_common.h" + +enum { + LEGACY_OPEN = 0, + LEGACY_SAVE = 1, + LEGACY_MULTI = 2 +}; + +// returns the length of a wchar_t containing multiple strings +static size_t wchar_len_multi(const wchar_t *str) { + const wchar_t *s = str; + while(*s) + s += wcslen(s) + 1; // advance to next substring + return (s - str) + 1; // account for additional terminator at end of list +} + +// converts wchar_t to nfdchar_t (utf8), returning a pointer to the created nfdchar_t (don't forget to free it) +static nfdchar_t *wchar_to_nfd(const wchar_t *src, const size_t src_ct) { + size_t sz, written; + nfdchar_t *dst = NULL; + sz = WideCharToMultiByte(CP_UTF8, 0, + src, src_ct, + NULL, 0, + NULL, NULL); + assert(sz); + sz++; + dst = (nfdchar_t*)NFDi_Malloc(sz); + if(!dst) + return NULL; + written = WideCharToMultiByte(CP_UTF8, 0, + src, src_ct, + dst, sz, + NULL, NULL); + assert(written); + + return dst; +} + +// converts nfdchar_t to wchar_t, returning a pointer to the created wchar_t (don't forget to free it) +// this function is also useful for converting NFD's UTF8 output for use with _wfopen() +// it is importantant that `len` is defined separately, because this works on strings containing multiple substrings and therefore multiple terminators +static wchar_t *nfd_to_wchar(const nfdchar_t *str, const size_t len) { + if(!len) + return NULL; + size_t wnum, written; + wchar_t *dst; + wnum = MultiByteToWideChar(CP_UTF8, 0, + str, len+1/*+1 terminator*/, + NULL, 0); + assert(wnum); + wnum++; + dst = (wchar_t*) NFDi_Malloc( wnum * sizeof(wchar_t) ); + if(!dst) + return NULL; + written = MultiByteToWideChar(CP_UTF8, 0, + str, len+1/*+1 terminator*/, + dst, wnum); + assert(written); + + return dst; +} + +// generates a filter and returns its length +// don't forget to free the result +static inline int filter_compose(char **dst, const nfdchar_t *src) { + if(src==NULL) + return 0; + const nfdchar_t *t, *end; + const size_t len = strlen(src) + 1; + char *p, *buf, *b; + end = src + len; // +1 to include terminator, which we treat the same as ';' when parsing + *dst = (char*) NFDi_Calloc ( 1, len * 4 ); // "png,jpg;txt\0" (11+1) -> "*.png;*.jpg\0*.png;*.jpg\0*.txt\0*.txt\0" (32+4) + if(!*dst) + return 0; + buf = (char*) NFDi_Calloc ( 1, len * 3 ); // extra buffer to simplify naming + if(!buf) { + NFDi_Free(*dst); + *dst = NULL; + return 0; + } + p = *dst; + memcpy(buf, "*.", 2); // precedes first filter + b = buf + 2; + for(t=src; tcount = 0; + dir_sz = strlen(dir); + p = t + dir_sz + 1; + while(*p) { + sz += dir_sz + strlen(p) + 1 + 1; // +1 to account for the separators we're adding, +1 to account for each path's terminator + outPaths->count++; + p += strlen(p) + 1; // proceed to next + } + if(!outPaths->count) { // only one file provided + outPaths->buf = t; + outPaths->indices = (size_t*) NFDi_Calloc( 1, sizeof(size_t) ); // calloc guarantees indices[0] = 0 + outPaths->count = 1; + return result; + } + outPaths->buf = (nfdchar_t*) NFDi_Calloc( sz, sizeof(nfdchar_t) ); + outPaths->indices = (size_t*) NFDi_Malloc( outPaths->count * sizeof(size_t) ); + for(i=0, k=outPaths->buf, p=t; icount; i++) { + p += strlen(p) + 1; // proceed to next + if(!*p) // two '\0's in a row, end of list + break; + outPaths->indices[i] = k - outPaths->buf; + memcpy(k, dir, dir_sz); + k += dir_sz; + *k++ = '\\'; // win32 path separator + sz = strlen(p); + memcpy(k, p, sz); + k += sz; + *k++ = '\0'; // terminator + } + +cleanup: + if(t) + NFDi_Free(t); + return result; +} + +nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList, + const nfdchar_t *defaultPath, + nfdchar_t **outPath ) +{ + return legacy_function(filterList, defaultPath, outPath, LEGACY_SAVE); +} + +nfdresult_t NFD_PickFolder( const nfdchar_t *defaultPath, + nfdchar_t **outPath ) +{ + _NFD_UNUSED(defaultPath); // TODO `defaultPath` is currently unused; what's a reliable way to put it to good use in legacy Win32? + BROWSEINFO binfo; + LPITEMIDLIST lpi; + wchar_t wbuf[2048]; + + // initialize binfo structure + ZeroMemory(&binfo, sizeof binfo); + binfo.pszDisplayName = wbuf; + binfo.iImage = -1; + binfo.ulFlags |= BIF_USENEWUI | BIF_VALIDATE | BIF_EDITBOX; + + // prompt + lpi = SHBrowseForFolder(&binfo); + + // error testing + if(!lpi) + return NFD_CANCEL; + if(!SHGetPathFromIDList(lpi, wbuf)) { + NFDi_SetError("SHGetPathFromIDList error"); + return NFD_ERROR; + } + + // convert to utf8 + *outPath = wchar_to_nfd(wbuf, wcslen(wbuf)+1); + if(!*outPath) { + NFDi_SetError("wchar_to_nfd(wbuf) allocation error"); + return NFD_ERROR; + } + + return NFD_OKAY; +}