| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275 |
- // 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.
- // https://github.com/mlabbe/nativefiledialog
- #include <stdlib.h>
- #include <assert.h>
- #include <string.h>
- #include <stdio.h>
- #include "nfd.h"
- #include "../iron_global.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');
- }
- #ifdef IRON_LINUX
- #include <gtk/gtk.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
- #ifdef IRON_WINDOWS
- #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
- #define _CRTDBG_MAP_ALLOC
- #include <stdlib.h>
- #include <crtdbg.h>
- /* only locally define UNICODE in this compilation unit */
- #ifndef UNICODE
- #define UNICODE
- #endif
- #include <wchar.h>
- #include <stdio.h>
- #include <assert.h>
- #include <windows.h>
- #include <shobjidl.h>
- #define COM_INITFLAGS COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE
- static BOOL COMIsInitialized(HRESULT coResult)
- {
- if (coResult == RPC_E_CHANGED_MODE)
- {
- // If COM was previously initialized with different init flags,
- // NFD still needs to operate. Eat this warning.
- return TRUE;
- }
- return SUCCEEDED(coResult);
- }
- static HRESULT COMInit(void)
- {
- return CoInitializeEx(NULL, COM_INITFLAGS);
- }
- static void COMUninit(HRESULT coResult)
- {
- // do not uninitialize if RPC_E_CHANGED_MODE occurred -- this
- // case does not refcount COM.
- if (SUCCEEDED(coResult))
- CoUninitialize();
- }
- // allocs the space in outPath -- call free()
- static void CopyWCharToNFDChar( const wchar_t *inStr, nfdchar_t **outStr )
- {
- int inStrCharacterCount = (int)(wcslen(inStr));
- int bytesNeeded = WideCharToMultiByte( CP_UTF8, 0,
- inStr, inStrCharacterCount,
- NULL, 0, NULL, NULL );
- assert( bytesNeeded );
- bytesNeeded += 1;
- *outStr = (nfdchar_t*)NFDi_Malloc( bytesNeeded );
- if ( !*outStr )
- return;
- int bytesWritten = WideCharToMultiByte( CP_UTF8, 0,
- inStr, -1,
- *outStr, bytesNeeded,
- NULL, NULL );
- assert( bytesWritten ); _NFD_UNUSED( bytesWritten );
- }
- /* includes NULL terminator byte in return */
- static size_t GetUTF8ByteCountForWChar( const wchar_t *str )
- {
- size_t bytesNeeded = WideCharToMultiByte( CP_UTF8, 0,
- str, -1,
- NULL, 0, NULL, NULL );
- assert( bytesNeeded );
- return bytesNeeded+1;
- }
- // write to outPtr -- no free() necessary.
- static int CopyWCharToExistingNFDCharBuffer( const wchar_t *inStr, nfdchar_t *outPtr )
- {
- int bytesNeeded = (int)(GetUTF8ByteCountForWChar( inStr ));
- /* invocation copies null term */
- int bytesWritten = WideCharToMultiByte( CP_UTF8, 0,
- inStr, -1,
- outPtr, bytesNeeded,
- NULL, 0 );
- assert( bytesWritten );
- return bytesWritten;
- }
- // allocs the space in outStr -- call free()
- static void CopyNFDCharToWChar( const nfdchar_t *inStr, wchar_t **outStr )
- {
- int inStrByteCount = (int)(strlen(inStr));
- int charsNeeded = MultiByteToWideChar(CP_UTF8, 0,
- inStr, inStrByteCount,
- NULL, 0 );
- assert( charsNeeded );
- assert( !*outStr );
- charsNeeded += 1; // terminator
- *outStr = (wchar_t*)NFDi_Malloc( charsNeeded * sizeof(wchar_t) );
- if ( !*outStr )
- return;
- int ret = MultiByteToWideChar(CP_UTF8, 0,
- inStr, inStrByteCount,
- *outStr, charsNeeded);
- (*outStr)[charsNeeded-1] = '\0';
- #ifdef _DEBUG
- int inStrCharacterCount = (int)(NFDi_UTF8_Strlen(inStr));
- assert( ret == inStrCharacterCount );
- #else
- _NFD_UNUSED(ret);
- #endif
- }
- /* ext is in format "jpg", no wildcards or separators */
- static int AppendExtensionToSpecBuf( const char *ext, char *specBuf, size_t specBufLen )
- {
- const char SEP[] = ";";
- assert( specBufLen > strlen(ext)+3 );
- if ( strlen(specBuf) > 0 )
- {
- strncat( specBuf, SEP, specBufLen - strlen(specBuf) - 1 );
- specBufLen += strlen(SEP);
- }
- char extWildcard[NFD_MAX_STRLEN];
- int bytesWritten = sprintf_s( extWildcard, NFD_MAX_STRLEN, "*.%s", ext );
- assert( bytesWritten == (int)(strlen(ext)+2) );
- _NFD_UNUSED(bytesWritten);
- strncat( specBuf, extWildcard, specBufLen - strlen(specBuf) - 1 );
- return NFD_OKAY;
- }
- static nfdresult_t AddFiltersToDialog( IFileDialog *fileOpenDialog, const char *filterList )
- {
- const wchar_t WILDCARD[] = L"*.*";
- if ( !filterList || strlen(filterList) == 0 )
- return NFD_OKAY;
- // Count rows to alloc
- UINT filterCount = 1; /* guaranteed to have one filter on a correct, non-empty parse */
- const char *p_filterList;
- for ( p_filterList = filterList; *p_filterList; ++p_filterList )
- {
- if ( *p_filterList == ';' )
- ++filterCount;
- }
- assert(filterCount);
- if ( !filterCount )
- {
- NFDi_SetError("Error parsing filters.");
- return NFD_ERROR;
- }
- /* filterCount plus 1 because we hardcode the *.* wildcard after the while loop */
- COMDLG_FILTERSPEC *specList = (COMDLG_FILTERSPEC*)NFDi_Malloc( sizeof(COMDLG_FILTERSPEC) * ((size_t)filterCount + 1) );
- if ( !specList )
- {
- return NFD_ERROR;
- }
- for (UINT i = 0; i < filterCount+1; ++i )
- {
- specList[i].pszName = NULL;
- specList[i].pszSpec = NULL;
- }
- size_t specIdx = 0;
- p_filterList = filterList;
- char typebuf[NFD_MAX_STRLEN] = {0}; /* one per comma or semicolon */
- char *p_typebuf = typebuf;
- char specbuf[NFD_MAX_STRLEN] = {0}; /* one per semicolon */
- while ( 1 )
- {
- if ( NFDi_IsFilterSegmentChar(*p_filterList) )
- {
- /* append a type to the specbuf (pending filter) */
- AppendExtensionToSpecBuf( typebuf, specbuf, 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 specList */
- CopyNFDCharToWChar( specbuf, (wchar_t**)&specList[specIdx].pszName );
- CopyNFDCharToWChar( specbuf, (wchar_t**)&specList[specIdx].pszSpec );
- memset( specbuf, 0, sizeof(char)*NFD_MAX_STRLEN );
- ++specIdx;
- if ( specIdx == filterCount )
- break;
- }
- if ( !NFDi_IsFilterSegmentChar( *p_filterList ))
- {
- *p_typebuf = *p_filterList;
- ++p_typebuf;
- }
- ++p_filterList;
- }
- /* Add wildcard */
- specList[specIdx].pszSpec = WILDCARD;
- specList[specIdx].pszName = WILDCARD;
- fileOpenDialog->lpVtbl->SetFileTypes(fileOpenDialog, filterCount + 1, specList);
- /* free speclist */
- for ( size_t i = 0; i < filterCount; ++i )
- {
- NFDi_Free( (void*)specList[i].pszSpec );
- }
- NFDi_Free( specList );
- return NFD_OKAY;
- }
- static nfdresult_t AllocPathSet( IShellItemArray *shellItems, nfdpathset_t *pathSet )
- {
- const char ERRORMSG[] = "Error allocating pathset.";
- assert(shellItems);
- assert(pathSet);
- // How many items in shellItems?
- DWORD numShellItems;
- HRESULT result = shellItems->lpVtbl->GetCount(shellItems, & numShellItems);
- if ( !SUCCEEDED(result) )
- {
- NFDi_SetError(ERRORMSG);
- return NFD_ERROR;
- }
- pathSet->count = (size_t)(numShellItems);
- assert( pathSet->count > 0 );
- pathSet->indices = (size_t*)NFDi_Malloc( sizeof(size_t)*pathSet->count );
- if ( !pathSet->indices )
- {
- return NFD_ERROR;
- }
- /* count the total bytes needed for buf */
- size_t bufSize = 0;
- for ( DWORD i = 0; i < numShellItems; ++i )
- {
- IShellItem *shellItem;
- result = shellItems->lpVtbl->GetItemAt(shellItems, i, &shellItem);
- if ( !SUCCEEDED(result) )
- {
- NFDi_SetError(ERRORMSG);
- return NFD_ERROR;
- }
- // Confirm SFGAO_FILESYSTEM is true for this shellitem, or ignore it.
- SFGAOF attribs;
- result = shellItem->lpVtbl->GetAttributes(shellItem, SFGAO_FILESYSTEM, &attribs);
- if ( !SUCCEEDED(result) )
- {
- NFDi_SetError(ERRORMSG);
- return NFD_ERROR;
- }
- if ( !(attribs & SFGAO_FILESYSTEM) )
- continue;
- LPWSTR name;
- shellItem->lpVtbl->GetDisplayName(shellItem, SIGDN_FILESYSPATH, &name);
- // Calculate length of name with UTF-8 encoding
- bufSize += GetUTF8ByteCountForWChar( name );
- CoTaskMemFree(name);
- }
- assert(bufSize);
- pathSet->buf = (nfdchar_t*)NFDi_Malloc( sizeof(nfdchar_t) * bufSize );
- memset( pathSet->buf, 0, sizeof(nfdchar_t) * bufSize );
- /* fill buf */
- nfdchar_t *p_buf = pathSet->buf;
- for (DWORD i = 0; i < numShellItems; ++i )
- {
- IShellItem *shellItem;
- result = shellItems->lpVtbl->GetItemAt(shellItems, i, &shellItem);
- if ( !SUCCEEDED(result) )
- {
- NFDi_SetError(ERRORMSG);
- return NFD_ERROR;
- }
- // Confirm SFGAO_FILESYSTEM is true for this shellitem, or ignore it.
- SFGAOF attribs;
- result = shellItem->lpVtbl->GetAttributes(shellItem, SFGAO_FILESYSTEM, &attribs);
- if ( !SUCCEEDED(result) )
- {
- NFDi_SetError(ERRORMSG);
- return NFD_ERROR;
- }
- if ( !(attribs & SFGAO_FILESYSTEM) )
- continue;
- LPWSTR name;
- shellItem->lpVtbl->GetDisplayName(shellItem, SIGDN_FILESYSPATH, &name);
- int bytesWritten = CopyWCharToExistingNFDCharBuffer(name, p_buf);
- CoTaskMemFree(name);
- ptrdiff_t index = p_buf - pathSet->buf;
- assert( index >= 0 );
- pathSet->indices[i] = (size_t)(index);
- p_buf += bytesWritten;
- }
- return NFD_OKAY;
- }
- static nfdresult_t SetDefaultPath( IFileDialog *dialog, const char *defaultPath )
- {
- if ( !defaultPath || strlen(defaultPath) == 0 )
- return NFD_OKAY;
- wchar_t *defaultPathW = {0};
- CopyNFDCharToWChar( defaultPath, &defaultPathW );
- IShellItem *folder;
- HRESULT result = SHCreateItemFromParsingName(defaultPathW, NULL, &IID_IShellItem, (void **)&folder);
- // Valid non results.
- if ( result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || result == HRESULT_FROM_WIN32(ERROR_INVALID_DRIVE) )
- {
- NFDi_Free( defaultPathW );
- return NFD_OKAY;
- }
- if ( !SUCCEEDED(result) )
- {
- NFDi_SetError("Error creating ShellItem");
- NFDi_Free( defaultPathW );
- return NFD_ERROR;
- }
- // Could also call SetDefaultFolder(), but this guarantees defaultPath -- more consistency across API.
- dialog->lpVtbl->SetFolder(dialog, folder);
- NFDi_Free( defaultPathW );
- folder->lpVtbl->Release(folder);
- return NFD_OKAY;
- }
- /* public */
- nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList,
- const nfdchar_t *defaultPath,
- nfdchar_t **outPath )
- {
- nfdresult_t nfdResult = NFD_ERROR;
- HRESULT coResult = COMInit();
- if (!COMIsInitialized(coResult))
- {
- NFDi_SetError("Could not initialize COM.");
- return nfdResult;
- }
- // Create dialog
- IFileOpenDialog *fileOpenDialog;
- HRESULT result = CoCreateInstance(&CLSID_FileOpenDialog, NULL,
- CLSCTX_ALL, &IID_IFileOpenDialog,
- (void**)(&fileOpenDialog) );
- if ( !SUCCEEDED(result) )
- {
- NFDi_SetError("Could not create dialog.");
- goto end;
- }
- // Build the filter list
- if ( !AddFiltersToDialog( fileOpenDialog, filterList ) )
- {
- goto end;
- }
- // Set the default path
- if ( !SetDefaultPath( fileOpenDialog, defaultPath ) )
- {
- goto end;
- }
- // Show the dialog.
- result = fileOpenDialog->lpVtbl->Show(fileOpenDialog, NULL);
- if ( SUCCEEDED(result) )
- {
- // Get the file name
- IShellItem *shellItem;
- result = fileOpenDialog->lpVtbl->GetResult(fileOpenDialog , & shellItem);
- if ( !SUCCEEDED(result) )
- {
- NFDi_SetError("Could not get shell item from dialog.");
- goto end;
- }
- wchar_t *filePath;
- result = shellItem->lpVtbl->GetDisplayName(shellItem, SIGDN_FILESYSPATH, &filePath);
- if ( !SUCCEEDED(result) )
- {
- NFDi_SetError("Could not get file path for selected.");
- shellItem->lpVtbl->Release(shellItem);
- goto end;
- }
- CopyWCharToNFDChar( filePath, outPath );
- CoTaskMemFree(filePath);
- if ( !*outPath )
- {
- /* error is malloc-based, error message would be redundant */
- shellItem->lpVtbl->Release(shellItem);
- goto end;
- }
- nfdResult = NFD_OKAY;
- shellItem->lpVtbl->Release(shellItem);
- }
- else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
- {
- nfdResult = NFD_CANCEL;
- }
- else
- {
- NFDi_SetError("File dialog box show failed.");
- nfdResult = NFD_ERROR;
- }
- end:
- if (fileOpenDialog)
- fileOpenDialog->lpVtbl->Release(fileOpenDialog);
- COMUninit(coResult);
- return nfdResult;
- }
- nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
- const nfdchar_t *defaultPath,
- nfdpathset_t *outPaths )
- {
- nfdresult_t nfdResult = NFD_ERROR;
- HRESULT coResult = COMInit();
- if (!COMIsInitialized(coResult))
- {
- NFDi_SetError("Could not initialize COM.");
- return nfdResult;
- }
- // Create dialog
- IFileOpenDialog *fileOpenDialog;
- HRESULT result = CoCreateInstance(&CLSID_FileOpenDialog, NULL,
- CLSCTX_ALL, &IID_IFileOpenDialog,
- (void**)(&fileOpenDialog) );
- if ( !SUCCEEDED(result) )
- {
- fileOpenDialog = NULL;
- NFDi_SetError("Could not create dialog.");
- goto end;
- }
- // Build the filter list
- if ( !AddFiltersToDialog( fileOpenDialog, filterList ) )
- {
- goto end;
- }
- // Set the default path
- if ( !SetDefaultPath( fileOpenDialog, defaultPath ) )
- {
- goto end;
- }
- // Set a flag for multiple options
- DWORD dwFlags;
- result = fileOpenDialog->lpVtbl->GetOptions(fileOpenDialog , & dwFlags);
- if ( !SUCCEEDED(result) )
- {
- NFDi_SetError("Could not get options.");
- goto end;
- }
- result = fileOpenDialog->lpVtbl->SetOptions(fileOpenDialog, dwFlags | FOS_ALLOWMULTISELECT);
- if ( !SUCCEEDED(result) )
- {
- NFDi_SetError("Could not set options.");
- goto end;
- }
- // Show the dialog.
- result = fileOpenDialog->lpVtbl->Show(fileOpenDialog, NULL);
- if ( SUCCEEDED(result) )
- {
- IShellItemArray *shellItems;
- result = fileOpenDialog->lpVtbl->GetResults(fileOpenDialog, & shellItems);
- if ( !SUCCEEDED(result) )
- {
- NFDi_SetError("Could not get shell items.");
- goto end;
- }
- if ( AllocPathSet( shellItems, outPaths ) == NFD_ERROR )
- {
- shellItems->lpVtbl->Release(shellItems);
- goto end;
- }
- shellItems->lpVtbl->Release(shellItems);
- nfdResult = NFD_OKAY;
- }
- else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
- {
- nfdResult = NFD_CANCEL;
- }
- else
- {
- NFDi_SetError("File dialog box show failed.");
- nfdResult = NFD_ERROR;
- }
- end:
- if ( fileOpenDialog )
- fileOpenDialog->lpVtbl->Release(fileOpenDialog);
- COMUninit(coResult);
- return nfdResult;
- }
- nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
- const nfdchar_t *defaultPath,
- nfdchar_t **outPath )
- {
- nfdresult_t nfdResult = NFD_ERROR;
- HRESULT coResult = COMInit();
- if (!COMIsInitialized(coResult))
- {
- NFDi_SetError("Could not initialize COM.");
- return nfdResult;
- }
- // Create dialog
- IFileSaveDialog *fileSaveDialog;
- HRESULT result = CoCreateInstance(&CLSID_FileSaveDialog, NULL,
- CLSCTX_ALL, &IID_IFileSaveDialog,
- (void**)(&fileSaveDialog) );
- if ( !SUCCEEDED(result) )
- {
- fileSaveDialog = NULL;
- NFDi_SetError("Could not create dialog.");
- goto end;
- }
- // Build the filter list
- if ( !AddFiltersToDialog( fileSaveDialog, filterList ) )
- {
- goto end;
- }
- // Set the default path
- if ( !SetDefaultPath( fileSaveDialog, defaultPath ) )
- {
- goto end;
- }
- // Show the dialog.
- result = fileSaveDialog->lpVtbl->Show(fileSaveDialog, NULL);
- if ( SUCCEEDED(result) )
- {
- // Get the file name
- IShellItem *shellItem;
- result = fileSaveDialog->lpVtbl->GetResult(fileSaveDialog , & shellItem);
- if ( !SUCCEEDED(result) )
- {
- NFDi_SetError("Could not get shell item from dialog.");
- goto end;
- }
- wchar_t *filePath;
- result = shellItem->lpVtbl->GetDisplayName(shellItem, SIGDN_FILESYSPATH, &filePath);
- if ( !SUCCEEDED(result) )
- {
- shellItem->lpVtbl->Release(shellItem);
- NFDi_SetError("Could not get file path for selected.");
- goto end;
- }
- CopyWCharToNFDChar( filePath, outPath );
- CoTaskMemFree(filePath);
- if ( !*outPath )
- {
- /* error is malloc-based, error message would be redundant */
- shellItem->lpVtbl->Release(shellItem);
- goto end;
- }
- nfdResult = NFD_OKAY;
- shellItem->lpVtbl->Release(shellItem);
- }
- else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
- {
- nfdResult = NFD_CANCEL;
- }
- else
- {
- NFDi_SetError("File dialog box show failed.");
- nfdResult = NFD_ERROR;
- }
- end:
- if ( fileSaveDialog )
- fileSaveDialog->lpVtbl->Release(fileSaveDialog);
- COMUninit(coResult);
- return nfdResult;
- }
- nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath,
- nfdchar_t **outPath)
- {
- nfdresult_t nfdResult = NFD_ERROR;
- DWORD dwOptions = 0;
- HRESULT coResult = COMInit();
- if (!COMIsInitialized(coResult))
- {
- NFDi_SetError("CoInitializeEx failed.");
- return nfdResult;
- }
- // Create dialog
- IFileOpenDialog *fileDialog;
- HRESULT result = CoCreateInstance(&CLSID_FileOpenDialog,
- NULL,
- CLSCTX_ALL, &IID_IFileOpenDialog, (void **)&fileDialog);
- if ( !SUCCEEDED(result) )
- {
- NFDi_SetError("CoCreateInstance for CLSID_FileOpenDialog failed.");
- goto end;
- }
- // Set the default path
- if (SetDefaultPath(fileDialog, defaultPath) != NFD_OKAY)
- {
- NFDi_SetError("SetDefaultPath failed.");
- goto end;
- }
- // Get the dialogs options
- if (!SUCCEEDED(fileDialog->lpVtbl->GetOptions(fileDialog , & dwOptions)))
- {
- NFDi_SetError("GetOptions for IFileDialog failed.");
- goto end;
- }
- // Add in FOS_PICKFOLDERS which hides files and only allows selection of folders
- if (!SUCCEEDED(fileDialog->lpVtbl->SetOptions(fileDialog, dwOptions | FOS_PICKFOLDERS)))
- {
- NFDi_SetError("SetOptions for IFileDialog failed.");
- goto end;
- }
- // Show the dialog to the user
- result = fileDialog->lpVtbl->Show(fileDialog, NULL);
- if ( SUCCEEDED(result) )
- {
- // Get the folder name
- IShellItem *shellItem;
- result = fileDialog->lpVtbl->GetResult(fileDialog , & shellItem);
- if ( !SUCCEEDED(result) )
- {
- NFDi_SetError("Could not get file path for selected.");
- shellItem->lpVtbl->Release(shellItem);
- goto end;
- }
- wchar_t *path = NULL;
- result = shellItem->lpVtbl->GetDisplayName(shellItem, SIGDN_DESKTOPABSOLUTEPARSING, &path);
- if ( !SUCCEEDED(result) )
- {
- NFDi_SetError("GetDisplayName for IShellItem failed.");
- shellItem->lpVtbl->Release(shellItem);
- goto end;
- }
- CopyWCharToNFDChar(path, outPath);
- CoTaskMemFree(path);
- if ( !*outPath )
- {
- shellItem->lpVtbl->Release(shellItem);
- goto end;
- }
- nfdResult = NFD_OKAY;
- shellItem->lpVtbl->Release(shellItem);
- }
- else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
- {
- nfdResult = NFD_CANCEL;
- }
- else
- {
- NFDi_SetError("Show for IFileDialog failed.");
- nfdResult = NFD_ERROR;
- }
- end:
- if (fileDialog)
- fileDialog->lpVtbl->Release(fileDialog);
- COMUninit(coResult);
- return nfdResult;
- }
- #endif
|