Add native file dialog library.

https://github.com/mlabbe/nativefiledialog.git
5cfe5002eb0fac1e49777a17dec70134147931e2
This commit is contained in:
Bartosz Taudul 2017-09-30 14:21:34 +02:00
parent d7bd8885fe
commit 65d6ef7ef4
8 changed files with 1698 additions and 0 deletions

16
nfd/LICENSE Executable file
View File

@ -0,0 +1,16 @@
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.

21
nfd/common.h Executable file
View File

@ -0,0 +1,21 @@
/*
Native File Dialog
Internal, common across platforms
http://www.frogtoss.com/labs
*/
#ifndef _NFD_COMMON_H
#define _NFD_COMMON_H
#define NFD_MAX_STRLEN 256
#define _NFD_UNUSED(x) ((void)x)
void *NFDi_Malloc( size_t bytes );
void NFDi_Free( void *ptr );
void NFDi_SetError( const char *msg );
void NFDi_SafeStrncpy( char *dst, const char *src, size_t maxCopy );
#endif

74
nfd/nfd.h Executable file
View File

@ -0,0 +1,74 @@
/*
Native File Dialog
User API
http://www.frogtoss.com/labs
*/
#ifndef _NFD_H
#define _NFD_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
/* denotes UTF-8 char */
typedef char nfdchar_t;
/* opaque data structure -- see NFD_PathSet_* */
typedef struct {
nfdchar_t *buf;
size_t *indices; /* byte offsets into buf */
size_t count; /* number of indices into buf */
}nfdpathset_t;
typedef enum {
NFD_ERROR, /* programmatic error */
NFD_OKAY, /* user pressed okay, or successful return */
NFD_CANCEL /* user pressed cancel */
}nfdresult_t;
/* nfd_<targetplatform>.c */
/* single file open dialog */
nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath );
/* multiple file open dialog */
nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdpathset_t *outPaths );
/* save dialog */
nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath );
/* select folder dialog */
nfdresult_t NFD_PickFolder( const nfdchar_t *defaultPath,
nfdchar_t **outPath);
/* nfd_common.c */
/* get last error -- set when nfdresult_t returns NFD_ERROR */
const char *NFD_GetError( void );
/* get the number of entries stored in pathSet */
size_t NFD_PathSet_GetCount( const nfdpathset_t *pathSet );
/* Get the UTF-8 path at offset index */
nfdchar_t *NFD_PathSet_GetPath( const nfdpathset_t *pathSet, size_t index );
/* Free the pathSet */
void NFD_PathSet_Free( nfdpathset_t *pathSet );
#ifdef __cplusplus
}
#endif
#endif

276
nfd/nfd_cocoa.m Executable file
View File

@ -0,0 +1,276 @@
/*
Native File Dialog
http://www.frogtoss.com/labs
*/
#include <AppKit/AppKit.h>
#include "nfd.h"
#include "nfd_common.h"
static NSArray *BuildAllowedFileTypes( const char *filterList )
{
// Commas and semicolons are the same thing on this platform
NSMutableArray *buildFilterList = [[NSMutableArray alloc] init];
char typebuf[NFD_MAX_STRLEN] = {0};
size_t filterListLen = strlen(filterList);
char *p_typebuf = typebuf;
for ( size_t i = 0; i < filterListLen+1; ++i )
{
if ( filterList[i] == ',' || filterList[i] == ';' || filterList[i] == '\0' )
{
++p_typebuf;
*p_typebuf = '\0';
NSString *thisType = [NSString stringWithUTF8String: typebuf];
[buildFilterList addObject:thisType];
p_typebuf = typebuf;
*p_typebuf = '\0';
}
else
{
*p_typebuf = filterList[i];
++p_typebuf;
}
}
NSArray *returnArray = [NSArray arrayWithArray:buildFilterList];
[buildFilterList release];
return returnArray;
}
static void AddFilterListToDialog( NSSavePanel *dialog, const char *filterList )
{
if ( !filterList || strlen(filterList) == 0 )
return;
NSArray *allowedFileTypes = BuildAllowedFileTypes( filterList );
if ( [allowedFileTypes count] != 0 )
{
[dialog setAllowedFileTypes:allowedFileTypes];
}
}
static void SetDefaultPath( NSSavePanel *dialog, const nfdchar_t *defaultPath )
{
if ( !defaultPath || strlen(defaultPath) == 0 )
return;
NSString *defaultPathString = [NSString stringWithUTF8String: defaultPath];
NSURL *url = [NSURL fileURLWithPath:defaultPathString isDirectory:YES];
[dialog setDirectoryURL:url];
}
/* fixme: pathset should be pathSet */
static nfdresult_t AllocPathSet( NSArray *urls, nfdpathset_t *pathset )
{
assert(pathset);
assert([urls count]);
pathset->count = (size_t)[urls count];
pathset->indices = NFDi_Malloc( sizeof(size_t)*pathset->count );
if ( !pathset->indices )
{
return NFD_ERROR;
}
// count the total space needed for buf
size_t bufsize = 0;
for ( NSURL *url in urls )
{
NSString *path = [url path];
bufsize += [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
}
pathset->buf = NFDi_Malloc( sizeof(nfdchar_t) * bufsize );
if ( !pathset->buf )
{
return NFD_ERROR;
}
// fill buf
nfdchar_t *p_buf = pathset->buf;
size_t count = 0;
for ( NSURL *url in urls )
{
NSString *path = [url path];
const nfdchar_t *utf8Path = [path UTF8String];
size_t byteLen = [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
memcpy( p_buf, utf8Path, byteLen );
ptrdiff_t index = p_buf - pathset->buf;
assert( index >= 0 );
pathset->indices[count] = (size_t)index;
p_buf += byteLen;
++count;
}
return NFD_OKAY;
}
/* public */
nfdresult_t NFD_OpenDialog( const char *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath )
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
NSOpenPanel *dialog = [NSOpenPanel openPanel];
[dialog setAllowsMultipleSelection:NO];
// Build the filter list
AddFilterListToDialog(dialog, filterList);
// Set the starting directory
SetDefaultPath(dialog, defaultPath);
nfdresult_t nfdResult = NFD_CANCEL;
if ( [dialog runModal] == NSModalResponseOK )
{
NSURL *url = [dialog URL];
const char *utf8Path = [[url path] UTF8String];
// byte count, not char count
size_t len = strlen(utf8Path);//NFDi_UTF8_Strlen(utf8Path);
*outPath = NFDi_Malloc( len+1 );
if ( !*outPath )
{
[pool release];
return NFD_ERROR;
}
memcpy( *outPath, utf8Path, len+1 ); /* copy null term */
nfdResult = NFD_OKAY;
}
[pool release];
[keyWindow makeKeyAndOrderFront:nil];
return nfdResult;
}
nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdpathset_t *outPaths )
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSOpenPanel *dialog = [NSOpenPanel openPanel];
[dialog setAllowsMultipleSelection:YES];
// Build the fiter list.
AddFilterListToDialog(dialog, filterList);
// Set the starting directory
SetDefaultPath(dialog, defaultPath);
nfdresult_t nfdResult = NFD_CANCEL;
if ( [dialog runModal] == NSModalResponseOK )
{
NSArray *urls = [dialog URLs];
if ( [urls count] == 0 )
{
[pool release];
return NFD_CANCEL;
}
if ( AllocPathSet( urls, outPaths ) == NFD_ERROR )
{
[pool release];
return NFD_ERROR;
}
nfdResult = NFD_OKAY;
}
[pool release];
return nfdResult;
}
nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath )
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSSavePanel *dialog = [NSSavePanel savePanel];
[dialog setExtensionHidden:NO];
// Build the filter list.
AddFilterListToDialog(dialog, filterList);
// Set the starting directory
SetDefaultPath(dialog, defaultPath);
nfdresult_t nfdResult = NFD_CANCEL;
if ( [dialog runModal] == NSModalResponseOK )
{
NSURL *url = [dialog URL];
const char *utf8Path = [[url path] UTF8String];
size_t byteLen = [url.path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
*outPath = NFDi_Malloc( byteLen );
if ( !*outPath )
{
[pool release];
return NFD_ERROR;
}
memcpy( *outPath, utf8Path, byteLen );
nfdResult = NFD_OKAY;
}
[pool release];
return nfdResult;
}
nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath,
nfdchar_t **outPath)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
NSOpenPanel *dialog = [NSOpenPanel openPanel];
[dialog setAllowsMultipleSelection:NO];
[dialog setCanChooseDirectories:YES];
[dialog setCanCreateDirectories:YES];
[dialog setCanChooseFiles:NO];
// Set the starting directory
SetDefaultPath(dialog, defaultPath);
nfdresult_t nfdResult = NFD_CANCEL;
if ( [dialog runModal] == NSModalResponseOK )
{
NSURL *url = [dialog URL];
const char *utf8Path = [[url path] UTF8String];
// byte count, not char count
size_t len = strlen(utf8Path);//NFDi_UTF8_Strlen(utf8Path);
*outPath = NFDi_Malloc( len+1 );
if ( !*outPath )
{
[pool release];
return NFD_ERROR;
}
memcpy( *outPath, utf8Path, len+1 ); /* copy null term */
nfdResult = NFD_OKAY;
}
[pool release];
[keyWindow makeKeyAndOrderFront:nil];
return nfdResult;
}

142
nfd/nfd_common.c Executable file
View File

@ -0,0 +1,142 @@
/*
Native File Dialog
http://www.frogtoss.com/labs
*/
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include "nfd_common.h"
static char g_errorstr[NFD_MAX_STRLEN] = {0};
/* public routines */
const char *NFD_GetError( void )
{
return g_errorstr;
}
size_t NFD_PathSet_GetCount( const nfdpathset_t *pathset )
{
assert(pathset);
return pathset->count;
}
nfdchar_t *NFD_PathSet_GetPath( const nfdpathset_t *pathset, size_t num )
{
assert(pathset);
assert(num < pathset->count);
return pathset->buf + pathset->indices[num];
}
void NFD_PathSet_Free( nfdpathset_t *pathset )
{
assert(pathset);
NFDi_Free( pathset->indices );
NFDi_Free( pathset->buf );
}
/* internal routines */
void *NFDi_Malloc( size_t bytes )
{
void *ptr = malloc(bytes);
if ( !ptr )
NFDi_SetError("NFDi_Malloc failed.");
return ptr;
}
void NFDi_Free( void *ptr )
{
assert(ptr);
free(ptr);
}
void NFDi_SetError( const char *msg )
{
int bTruncate = NFDi_SafeStrncpy( g_errorstr, msg, NFD_MAX_STRLEN );
assert( !bTruncate ); _NFD_UNUSED(bTruncate);
}
int NFDi_SafeStrncpy( char *dst, const char *src, size_t maxCopy )
{
size_t n = maxCopy;
char *d = dst;
assert( src );
assert( dst );
while ( n > 0 && *src != '\0' )
{
*d++ = *src++;
--n;
}
/* Truncation case -
terminate string and return true */
if ( n == 0 )
{
dst[maxCopy-1] = '\0';
return 1;
}
/* No truncation. Append a single NULL and return. */
*d = '\0';
return 0;
}
/* adapted from microutf8 */
size_t NFDi_UTF8_Strlen( const nfdchar_t *str )
{
/* This function doesn't properly check validity of UTF-8 character
sequence, it is supposed to use only with valid UTF-8 strings. */
size_t character_count = 0;
size_t i = 0; /* Counter used to iterate over string. */
nfdchar_t maybe_bom[4];
/* If there is UTF-8 BOM ignore it. */
if (strlen(str) > 2)
{
strncpy(maybe_bom, str, 3);
maybe_bom[3] = 0;
if (strcmp(maybe_bom, (nfdchar_t*)NFD_UTF8_BOM) == 0)
i += 3;
}
while(str[i])
{
if (str[i] >> 7 == 0)
{
/* If bit pattern begins with 0 we have ascii character. */
++character_count;
}
else if (str[i] >> 6 == 3)
{
/* If bit pattern begins with 11 it is beginning of UTF-8 byte sequence. */
++character_count;
}
else if (str[i] >> 6 == 2)
; /* If bit pattern begins with 10 it is middle of utf-8 byte sequence. */
else
{
/* In any other case this is not valid UTF-8. */
return -1;
}
++i;
}
return character_count;
}
int NFDi_IsFilterSegmentChar( char ch )
{
return (ch==','||ch==';'||ch=='\0');
}

37
nfd/nfd_common.h Executable file
View File

@ -0,0 +1,37 @@
/*
Native File Dialog
Internal, common across platforms
http://www.frogtoss.com/labs
*/
#ifndef _NFD_COMMON_H
#define _NFD_COMMON_H
#include "nfd.h"
#ifdef __cplusplus
extern "C" {
#endif
#define NFD_MAX_STRLEN 256
#define _NFD_UNUSED(x) ((void)x)
#define NFD_UTF8_BOM "\xEF\xBB\xBF"
void *NFDi_Malloc( size_t bytes );
void NFDi_Free( void *ptr );
void NFDi_SetError( const char *msg );
int NFDi_SafeStrncpy( char *dst, const char *src, size_t maxCopy );
size_t NFDi_UTF8_Strlen( const nfdchar_t *str );
int NFDi_IsFilterSegmentChar( char ch );
#ifdef __cplusplus
}
#endif
#endif

379
nfd/nfd_gtk.c Executable file
View File

@ -0,0 +1,379 @@
/*
Native File Dialog
http://www.frogtoss.com/labs
*/
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <gtk/gtk.h>
#include "nfd.h"
#include "nfd_common.h"
const char INIT_FAIL_MSG[] = "gtk_init_check failed to initilaize GTK+";
static void AddTypeToFilterName( const char *typebuf, char *filterName, size_t bufsize )
{
const char SEP[] = ", ";
size_t len = strlen(filterName);
if ( len != 0 )
{
strncat( filterName, SEP, bufsize - len - 1 );
len += strlen(SEP);
}
strncat( filterName, typebuf, bufsize - len - 1 );
}
static void AddFiltersToDialog( GtkWidget *dialog, const char *filterList )
{
GtkFileFilter *filter;
char typebuf[NFD_MAX_STRLEN] = {0};
const char *p_filterList = filterList;
char *p_typebuf = typebuf;
char filterName[NFD_MAX_STRLEN] = {0};
if ( !filterList || strlen(filterList) == 0 )
return;
filter = gtk_file_filter_new();
while ( 1 )
{
if ( NFDi_IsFilterSegmentChar(*p_filterList) )
{
char typebufWildcard[NFD_MAX_STRLEN];
/* add another type to the filter */
assert( strlen(typebuf) > 0 );
assert( strlen(typebuf) < NFD_MAX_STRLEN-1 );
snprintf( typebufWildcard, NFD_MAX_STRLEN, "*.%s", typebuf );
AddTypeToFilterName( typebuf, filterName, NFD_MAX_STRLEN );
gtk_file_filter_add_pattern( filter, typebufWildcard );
p_typebuf = typebuf;
memset( typebuf, 0, sizeof(char) * NFD_MAX_STRLEN );
}
if ( *p_filterList == ';' || *p_filterList == '\0' )
{
/* end of filter -- add it to the dialog */
gtk_file_filter_set_name( filter, filterName );
gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter );
filterName[0] = '\0';
if ( *p_filterList == '\0' )
break;
filter = gtk_file_filter_new();
}
if ( !NFDi_IsFilterSegmentChar( *p_filterList ) )
{
*p_typebuf = *p_filterList;
p_typebuf++;
}
p_filterList++;
}
/* always append a wildcard option to the end*/
filter = gtk_file_filter_new();
gtk_file_filter_set_name( filter, "*.*" );
gtk_file_filter_add_pattern( filter, "*" );
gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter );
}
static void SetDefaultPath( GtkWidget *dialog, const char *defaultPath )
{
if ( !defaultPath || strlen(defaultPath) == 0 )
return;
/* GTK+ manual recommends not specifically setting the default path.
We do it anyway in order to be consistent across platforms.
If consistency with the native OS is preferred, this is the line
to comment out. -ml */
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), defaultPath );
}
static nfdresult_t AllocPathSet( GSList *fileList, nfdpathset_t *pathSet )
{
size_t bufSize = 0;
GSList *node;
nfdchar_t *p_buf;
size_t count = 0;
assert(fileList);
assert(pathSet);
pathSet->count = (size_t)g_slist_length( fileList );
assert( pathSet->count > 0 );
pathSet->indices = NFDi_Malloc( sizeof(size_t)*pathSet->count );
if ( !pathSet->indices )
{
return NFD_ERROR;
}
/* count the total space needed for buf */
for ( node = fileList; node; node = node->next )
{
assert(node->data);
bufSize += strlen( (const gchar*)node->data ) + 1;
}
pathSet->buf = NFDi_Malloc( sizeof(nfdchar_t) * bufSize );
/* fill buf */
p_buf = pathSet->buf;
for ( node = fileList; node; node = node->next )
{
nfdchar_t *path = (nfdchar_t*)(node->data);
size_t byteLen = strlen(path)+1;
ptrdiff_t index;
memcpy( p_buf, path, byteLen );
g_free(node->data);
index = p_buf - pathSet->buf;
assert( index >= 0 );
pathSet->indices[count] = (size_t)index;
p_buf += byteLen;
++count;
}
g_slist_free( fileList );
return NFD_OKAY;
}
static void WaitForCleanup(void)
{
while (gtk_events_pending())
gtk_main_iteration();
}
/* public */
nfdresult_t NFD_OpenDialog( const char *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;
}

753
nfd/nfd_win.cpp Executable file
View File

@ -0,0 +1,753 @@
/*
Native File Dialog
http://www.frogtoss.com/labs
*/
/* 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
#include <wchar.h>
#include <stdio.h>
#include <assert.h>
#include <windows.h>
#include <ShObjIdl.h>
#include "nfd_common.h"
// allocs the space in outPath -- call free()
static void CopyWCharToNFDChar( const wchar_t *inStr, nfdchar_t **outStr )
{
int inStrCharacterCount = static_cast<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 )
{
int bytesNeeded = WideCharToMultiByte( CP_UTF8, 0,
str, -1,
NULL, 0, NULL, NULL );
assert( bytesNeeded );
return bytesNeeded+1;
}
// write to outPtr -- no free() necessary. No memory stomp tests are done -- they must be done
// before entering this function.
static int CopyWCharToExistingNFDCharBuffer( const wchar_t *inStr, nfdchar_t *outPtr )
{
int inStrCharacterCount = static_cast<int>(wcslen(inStr));
int bytesNeeded = static_cast<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 = static_cast<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 = static_cast<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 == strlen(ext)+2 );
strncat( specBuf, extWildcard, specBufLen - strlen(specBuf) - 1 );
return NFD_OKAY;
}
static nfdresult_t AddFiltersToDialog( ::IFileDialog *fileOpenDialog, const char *filterList )
{
const wchar_t EMPTY_WSTR[] = L"";
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) * (filterCount + 1) );
if ( !specList )
{
return NFD_ERROR;
}
for (size_t 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 filterName[NFD_MAX_STRLEN] = {0};
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 */
// Empty filter name -- Windows describes them by extension.
specList[specIdx].pszName = EMPTY_WSTR;
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 = EMPTY_WSTR;
fileOpenDialog->SetFileTypes( 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->GetCount(&numShellItems);
if ( !SUCCEEDED(result) )
{
NFDi_SetError(ERRORMSG);
return NFD_ERROR;
}
pathSet->count = static_cast<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->GetItemAt(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->GetAttributes( SFGAO_FILESYSTEM, &attribs );
if ( !SUCCEEDED(result) )
{
NFDi_SetError(ERRORMSG);
return NFD_ERROR;
}
if ( !(attribs & SFGAO_FILESYSTEM) )
continue;
LPWSTR name;
shellItem->GetDisplayName(SIGDN_FILESYSPATH, &name);
// Calculate length of name with UTF-8 encoding
bufSize += GetUTF8ByteCountForWChar( 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->GetItemAt(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->GetAttributes( SFGAO_FILESYSTEM, &attribs );
if ( !SUCCEEDED(result) )
{
NFDi_SetError(ERRORMSG);
return NFD_ERROR;
}
if ( !(attribs & SFGAO_FILESYSTEM) )
continue;
LPWSTR name;
shellItem->GetDisplayName(SIGDN_FILESYSPATH, &name);
int bytesWritten = CopyWCharToExistingNFDCharBuffer(name, p_buf);
ptrdiff_t index = p_buf - pathSet->buf;
assert( index >= 0 );
pathSet->indices[i] = static_cast<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_PPV_ARGS(&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->SetFolder( folder );
NFDi_Free( defaultPathW );
folder->Release();
return NFD_OKAY;
}
/* public */
nfdresult_t NFD_OpenDialog( const char *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath )
{
nfdresult_t nfdResult = NFD_ERROR;
// Init COM library.
HRESULT result = ::CoInitializeEx(NULL,
::COINIT_APARTMENTTHREADED |
::COINIT_DISABLE_OLE1DDE );
::IFileOpenDialog *fileOpenDialog(NULL);
if ( !SUCCEEDED(result))
{
NFDi_SetError("Could not initialize COM.");
goto end;
}
// Create dialog
result = ::CoCreateInstance(::CLSID_FileOpenDialog, NULL,
CLSCTX_ALL, ::IID_IFileOpenDialog,
reinterpret_cast<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->Show(NULL);
if ( SUCCEEDED(result) )
{
// Get the file name
::IShellItem *shellItem(NULL);
result = fileOpenDialog->GetResult(&shellItem);
if ( !SUCCEEDED(result) )
{
NFDi_SetError("Could not get shell item from dialog.");
goto end;
}
wchar_t *filePath(NULL);
result = shellItem->GetDisplayName(::SIGDN_FILESYSPATH, &filePath);
if ( !SUCCEEDED(result) )
{
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 */
goto end;
}
nfdResult = NFD_OKAY;
shellItem->Release();
}
else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
{
nfdResult = NFD_CANCEL;
}
else
{
NFDi_SetError("File dialog box show failed.");
nfdResult = NFD_ERROR;
}
end:
::CoUninitialize();
return nfdResult;
}
nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdpathset_t *outPaths )
{
nfdresult_t nfdResult = NFD_ERROR;
// Init COM library.
HRESULT result = ::CoInitializeEx(NULL,
::COINIT_APARTMENTTHREADED |
::COINIT_DISABLE_OLE1DDE );
if ( !SUCCEEDED(result))
{
NFDi_SetError("Could not initialize COM.");
return NFD_ERROR;
}
::IFileOpenDialog *fileOpenDialog(NULL);
// Create dialog
result = ::CoCreateInstance(::CLSID_FileOpenDialog, NULL,
CLSCTX_ALL, ::IID_IFileOpenDialog,
reinterpret_cast<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;
}
// Set a flag for multiple options
DWORD dwFlags;
result = fileOpenDialog->GetOptions(&dwFlags);
if ( !SUCCEEDED(result) )
{
NFDi_SetError("Could not get options.");
goto end;
}
result = fileOpenDialog->SetOptions(dwFlags | FOS_ALLOWMULTISELECT);
if ( !SUCCEEDED(result) )
{
NFDi_SetError("Could not set options.");
goto end;
}
// Show the dialog.
result = fileOpenDialog->Show(NULL);
if ( SUCCEEDED(result) )
{
IShellItemArray *shellItems;
result = fileOpenDialog->GetResults( &shellItems );
if ( !SUCCEEDED(result) )
{
NFDi_SetError("Could not get shell items.");
goto end;
}
if ( AllocPathSet( shellItems, outPaths ) == NFD_ERROR )
{
goto end;
}
shellItems->Release();
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:
::CoUninitialize();
return nfdResult;
}
nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath )
{
nfdresult_t nfdResult = NFD_ERROR;
// Init COM library.
HRESULT result = ::CoInitializeEx(NULL,
::COINIT_APARTMENTTHREADED |
::COINIT_DISABLE_OLE1DDE );
if ( !SUCCEEDED(result))
{
NFDi_SetError("Could not initialize COM.");
return NFD_ERROR;
}
::IFileSaveDialog *fileSaveDialog(NULL);
// Create dialog
result = ::CoCreateInstance(::CLSID_FileSaveDialog, NULL,
CLSCTX_ALL, ::IID_IFileSaveDialog,
reinterpret_cast<void**>(&fileSaveDialog) );
if ( !SUCCEEDED(result) )
{
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->Show(NULL);
if ( SUCCEEDED(result) )
{
// Get the file name
::IShellItem *shellItem;
result = fileSaveDialog->GetResult(&shellItem);
if ( !SUCCEEDED(result) )
{
NFDi_SetError("Could not get shell item from dialog.");
goto end;
}
wchar_t *filePath(NULL);
result = shellItem->GetDisplayName(::SIGDN_FILESYSPATH, &filePath);
if ( !SUCCEEDED(result) )
{
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 */
goto end;
}
nfdResult = NFD_OKAY;
shellItem->Release();
}
else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
{
nfdResult = NFD_CANCEL;
}
else
{
NFDi_SetError("File dialog box show failed.");
nfdResult = NFD_ERROR;
}
end:
::CoUninitialize();
return nfdResult;
}
class AutoCoInit
{
public:
AutoCoInit()
{
mResult = ::CoInitializeEx(NULL,
::COINIT_APARTMENTTHREADED |
::COINIT_DISABLE_OLE1DDE);
}
~AutoCoInit()
{
if (SUCCEEDED(mResult))
{
::CoUninitialize();
}
}
HRESULT Result() const { return mResult; }
private:
HRESULT mResult;
};
// VS2010 hasn't got a copy of CComPtr - this was first added in the 2003 SDK, so we make our own small CComPtr instead
template<class T>
class ComPtr
{
public:
ComPtr() : mPtr(NULL) { }
~ComPtr()
{
if (mPtr)
{
mPtr->Release();
}
}
T* Ptr() const { return mPtr; }
T** operator&() { return &mPtr; }
T* operator->() const { return mPtr; }
private:
// Don't allow copy or assignment
ComPtr(const ComPtr&);
ComPtr& operator = (const ComPtr&) const;
T* mPtr;
};
nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath,
nfdchar_t **outPath)
{
// Init COM
AutoCoInit autoCoInit;
if (!SUCCEEDED(autoCoInit.Result()))
{
NFDi_SetError("CoInitializeEx failed.");
return NFD_ERROR;
}
// Create the file dialog COM object
ComPtr<IFileDialog> pFileDialog;
if (!SUCCEEDED(CoCreateInstance(CLSID_FileOpenDialog,
NULL,
CLSCTX_ALL,
IID_PPV_ARGS(&pFileDialog))))
{
NFDi_SetError("CoCreateInstance for CLSID_FileOpenDialog failed.");
return NFD_ERROR;
}
// Set the default path
if (SetDefaultPath(pFileDialog.Ptr(), defaultPath) != NFD_OKAY)
{
NFDi_SetError("SetDefaultPath failed.");
return NFD_ERROR;
}
// Get the dialogs options
DWORD dwOptions = 0;
if (!SUCCEEDED(pFileDialog->GetOptions(&dwOptions)))
{
NFDi_SetError("GetOptions for IFileDialog failed.");
return NFD_ERROR;
}
// Add in FOS_PICKFOLDERS which hides files and only allows selection of folders
if (!SUCCEEDED(pFileDialog->SetOptions(dwOptions | FOS_PICKFOLDERS)))
{
NFDi_SetError("SetOptions for IFileDialog failed.");
return NFD_ERROR;
}
// Show the dialog to the user
const HRESULT result = pFileDialog->Show(NULL);
if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED))
{
return NFD_CANCEL;
}
else if (!SUCCEEDED(result))
{
NFDi_SetError("Show for IFileDialog failed.");
return NFD_ERROR;
}
// Get the shell item result
ComPtr<IShellItem> pShellItem;
if (!SUCCEEDED(pFileDialog->GetResult(&pShellItem)))
{
return NFD_OKAY;
}
// Finally get the path
wchar_t *path = NULL;
if (!SUCCEEDED(pShellItem->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &path)))
{
NFDi_SetError("GetDisplayName for IShellItem failed.");
return NFD_ERROR;
}
// Convert string
CopyWCharToNFDChar(path, outPath);
CoTaskMemFree(path);
if (!*outPath)
{
// error is malloc-based, error message would be redundant
return NFD_ERROR;
}
return NFD_OKAY;
}