mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-02-22 08:03:45 +00:00
Integrates the nativeFileDialog library to enable native file dialogs on the major platforms. It is activated with SDL.
This commit is contained in:
parent
bab55d46a9
commit
ec6f9c05a6
15 changed files with 2489 additions and 354 deletions
16
Engine/lib/nativeFileDialogs/LICENSE
Normal file
16
Engine/lib/nativeFileDialogs/LICENSE
Normal 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.
|
||||
|
||||
137
Engine/lib/nativeFileDialogs/README.md
Normal file
137
Engine/lib/nativeFileDialogs/README.md
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
# Native File Dialog #
|
||||
|
||||
A tiny, neat C library that portably invokes native file open and save dialogs. Write dialog code once and have it pop up native dialogs on all supported platforms. Avoid linking large dependencies like wxWidgets and qt.
|
||||
|
||||
Features:
|
||||
|
||||
- Lean C API, static library -- no ObjC, no C++, no STL.
|
||||
- Zlib licensed.
|
||||
- Consistent UTF-8 support on all platforms.
|
||||
- Simple universal file filter syntax.
|
||||
- Paid support available.
|
||||
- Multiple file selection support.
|
||||
- 64-bit and 32-bit friendly.
|
||||
- GCC, Clang and Visual Studio supported.
|
||||
- No third party dependencies.
|
||||
- Support for Vista's modern `IFileDialog` on Windows.
|
||||
- Support for non-deprecated Cocoa APIs on OS X.
|
||||
- GTK+3 dialog on Linux.
|
||||
- Tested, works alongside [http://www.libsdl.org](SDL2) on all platforms, for the game developers out there.
|
||||
|
||||
# Example Usage #
|
||||
|
||||
```C
|
||||
#include <nfd.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main( void )
|
||||
{
|
||||
nfdchar_t *outPath = NULL;
|
||||
nfdresult_t result = NFD_OpenDialog( NULL, NULL, &outPath );
|
||||
|
||||
if ( result == NFD_OKAY ) {
|
||||
puts("Success!");
|
||||
puts(outPath);
|
||||
free(outPath);
|
||||
}
|
||||
else if ( result == NFD_CANCEL ) {
|
||||
puts("User pressed cancel.");
|
||||
}
|
||||
else {
|
||||
printf("Error: %s\n", NFD_GetError() );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
See [NFD.h](src/include/nfd.h) for more options.
|
||||
|
||||
# Screenshots #
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
## Building ##
|
||||
|
||||
NFD uses [SCons](http://www.scons.org) for cross-platform builds. After installing SCons, build it with:
|
||||
|
||||
cd src
|
||||
scons debug=[0,1]
|
||||
|
||||
Alternatively, you can avoid Scons by just including NFD files to your existing project:
|
||||
|
||||
1. Add all header files in `src/` and `src/include` to your project.
|
||||
2. Add `src/include` to your include search path or copy it into your existing search path.
|
||||
3. Add `src/nfd_common.c` to your project.
|
||||
4. Add `src/nfd_<platform>` to your project, where `<platform>` is the NFD backend for the platform you are fixing to build.
|
||||
5. On Visual Studio, define `_CRT_SECURE_NO_WARNINGS` to avoid warnings.
|
||||
|
||||
### Compiling Your Programs ###
|
||||
|
||||
1. Add `src/include` to your include search path.
|
||||
2. Add `nfd.lib` to the list of list of static libraries to link against.
|
||||
3. Add `src/` to the library search path.
|
||||
|
||||
On Linux, you must compile and link against GTK+. Recommend use of `pkg-config --cflags --libs gtk+-3.0`.
|
||||
|
||||
On Mac OS X, add `AppKit` to the list of frameworks.
|
||||
|
||||
On Windows, ensure you are building against `comctl32.lib`.
|
||||
|
||||
## Usage ##
|
||||
|
||||
See `NFD.h` for API calls. See `tests/*.c` for example code.
|
||||
|
||||
See `tests/SConstruct` for a working build script that compiles on all platforms.
|
||||
|
||||
## File Filter Syntax ##
|
||||
|
||||
There is a form of file filtering in every file dialog, but no consistent means of supporting it. NFD provides support for filtering files by groups of extensions, providing its own descriptions (where applicable) for the extensions.
|
||||
|
||||
A wildcard filter is always added to every dialog.
|
||||
|
||||
### Separators ###
|
||||
|
||||
- `;` Begin a new filter.
|
||||
- `,` Add a separate type to the filter.
|
||||
|
||||
#### Examples ####
|
||||
|
||||
`txt` The default filter is for text files. There is a wildcard option in a dropdown.
|
||||
|
||||
`png,jpg;psd` The default filter is for png and jpg files. A second filter is available for psd files. There is a wildcard option in a dropdown.
|
||||
|
||||
`NULL` Wildcard only.
|
||||
|
||||
## Iterating Over PathSets ##
|
||||
|
||||
See [test_opendialogmultiple.c](test/test_opendialogmultiple.c).
|
||||
|
||||
# Known Limitations #
|
||||
|
||||
I accept quality code patches, or will resolve these and other matters through support.
|
||||
|
||||
- No support for Windows XP's legacy dialogs such as `GetOpenFileName`.
|
||||
- No support for file filter names -- ex: "Image Files" (*.png, *.jpg). Nameless filters are supported, though.
|
||||
- No support for selecting folders instead of files.
|
||||
- On Linux, GTK+ cannot be uninitialized to save memory. Launching a file dialog costs memory. I am open to accepting an alternative `nfd_zenity.c` implementation which uses Zenity and pipes.
|
||||
|
||||
# Copyright and Credit #
|
||||
|
||||
Copyright © 2014 [Frogtoss Games](http://www.frogtoss.com), Inc.
|
||||
File [LICENSE](LICENSE) covers all files in this repo.
|
||||
|
||||
Native File Dialog by Michael Labbe
|
||||
<mike@frogtoss.com>
|
||||
|
||||
Tomasz Konojacki for [microutf8](http://puszcza.gnu.org.ua/software/microutf8/)
|
||||
|
||||
## Support ##
|
||||
|
||||
Directed support for this work is available from the original author under a paid agreement.
|
||||
|
||||
[Contact Frogtoss Games](http://www.frogtoss.com/pages/contact.html).
|
||||
99
Engine/lib/nativeFileDialogs/SConstruct
Normal file
99
Engine/lib/nativeFileDialogs/SConstruct
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
#
|
||||
# Native File Dialog
|
||||
#
|
||||
# Scons build script -- GCC, Clang, Visual Studio
|
||||
# Does not build test
|
||||
|
||||
|
||||
import os
|
||||
|
||||
|
||||
# target arch is build arch -- extend here for OS cross compiling
|
||||
target_os=str(Platform())
|
||||
|
||||
# Corresponds to TARGET_ARCH set to environ.
|
||||
target_arch = ARGUMENTS.get('target_arch', None)
|
||||
|
||||
# visual studio does not import from environment
|
||||
if target_os != 'win32':
|
||||
IMPORT_FROM_ENV =['CC', 'CXX', 'CFLAGS', 'CXXFLAGS', 'ARFLAGS']
|
||||
else:
|
||||
IMPORT_FROM_ENV =[]
|
||||
|
||||
|
||||
debug = int(ARGUMENTS.get( 'debug', 0 ))
|
||||
|
||||
nfd_files = ['nfd_common.c']
|
||||
|
||||
# Due to a Scons limitation, TARGET_ARCH cannot be appended to an existing environment.
|
||||
if target_arch != None:
|
||||
nfd_env = Environment( TARGET_ARCH=target_arch )
|
||||
else:
|
||||
nfd_env = Environment()
|
||||
|
||||
# import specific environment variables from the command line, overriding
|
||||
# Scons environment defaults
|
||||
for env_key in IMPORT_FROM_ENV:
|
||||
if env_key in os.environ:
|
||||
print "Making %s => %s" % ( env_key, os.environ[env_key] )
|
||||
nfd_env[env_key] = os.environ[env_key]
|
||||
|
||||
# Windows runtime library types
|
||||
win_rtl = {'debug': '/MDd',
|
||||
'release': '/MD'}
|
||||
|
||||
def set_debug(env):
|
||||
if target_os == 'win32':
|
||||
env.Append( CCFLAGS=['/Z7', # obj contains full symbols
|
||||
win_rtl['debug']
|
||||
])
|
||||
else:
|
||||
env.Append( CFLAGS=['-g'] )
|
||||
|
||||
|
||||
def set_release(env):
|
||||
if target_os == 'win32':
|
||||
env.Append( CCFLAGS=[win_rtl['release'],
|
||||
'/O2'] )
|
||||
else:
|
||||
env.Append( CFLAGS=['-O3'] )
|
||||
|
||||
|
||||
def set_warnings(env):
|
||||
if target_os == 'win32':
|
||||
env.Append( CCFLAGS=['/W3'],
|
||||
CPPDEFINES=['_CRT_SECURE_NO_WARNINGS'] )
|
||||
else:
|
||||
env.Append( CFLAGS=['-Wall', '-pedantic'] )
|
||||
|
||||
|
||||
def get_lib_name(base, is_debug):
|
||||
if is_debug:
|
||||
return base + '_d'
|
||||
else:
|
||||
return base
|
||||
|
||||
|
||||
# Cocoa OS X builds - clang
|
||||
if target_os == 'darwin':
|
||||
nfd_files.append('nfd_cocoa.m')
|
||||
nfd_env.CC='clang -fcolor-diagnostics'
|
||||
|
||||
# Linux GTK+ 3 builds - GCC
|
||||
elif target_os == 'posix':
|
||||
nfd_files.append('nfd_gtk.c')
|
||||
nfd_env.ParseConfig( 'pkg-config --cflags gtk+-3.0' )
|
||||
|
||||
# Windows builds - Visual Studio
|
||||
elif target_os == 'win32':
|
||||
nfd_files.append('nfd_win.cpp')
|
||||
|
||||
if debug:
|
||||
set_debug(nfd_env)
|
||||
else:
|
||||
set_release(nfd_env)
|
||||
|
||||
set_warnings(nfd_env)
|
||||
|
||||
nfd_env.Append( CPPPATH=['.','./include'] )
|
||||
nfd_env.StaticLibrary( get_lib_name('nfd', debug), nfd_files )
|
||||
21
Engine/lib/nativeFileDialogs/common.h
Normal file
21
Engine/lib/nativeFileDialogs/common.h
Normal 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
|
||||
69
Engine/lib/nativeFileDialogs/include/nfd.h
Normal file
69
Engine/lib/nativeFileDialogs/include/nfd.h
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
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 );
|
||||
|
||||
/* 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
|
||||
235
Engine/lib/nativeFileDialogs/nfd_cocoa.m
Normal file
235
Engine/lib/nativeFileDialogs/nfd_cocoa.m
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
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];
|
||||
|
||||
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];
|
||||
|
||||
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;
|
||||
}
|
||||
142
Engine/lib/nativeFileDialogs/nfd_common.c
Normal file
142
Engine/lib/nativeFileDialogs/nfd_common.c
Normal 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
Engine/lib/nativeFileDialogs/nfd_common.h
Normal file
37
Engine/lib/nativeFileDialogs/nfd_common.h
Normal 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
|
||||
326
Engine/lib/nativeFileDialogs/nfd_gtk.c
Normal file
326
Engine/lib/nativeFileDialogs/nfd_gtk.c
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
/*
|
||||
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;
|
||||
}
|
||||
619
Engine/lib/nativeFileDialogs/nfd_win.cpp
Normal file
619
Engine/lib/nativeFileDialogs/nfd_win.cpp
Normal file
|
|
@ -0,0 +1,619 @@
|
|||
/*
|
||||
Native File Dialog
|
||||
|
||||
http://www.frogtoss.com/labs
|
||||
*/
|
||||
|
||||
/* only locally define UNICODE in this compilation unit */
|
||||
#ifndef UNICODE
|
||||
#define UNICODE
|
||||
#endif
|
||||
|
||||
|
||||
#include <wchar.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <atlbase.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 );
|
||||
if ( !SUCCEEDED(result))
|
||||
{
|
||||
NFDi_SetError("Could not initialize COM.");
|
||||
goto end;
|
||||
}
|
||||
|
||||
::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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue