浏览代码

Integrates the nativeFileDialog library to enable native file dialogs on the major platforms. It is activated with SDL.

Areloch 9 年之前
父节点
当前提交
ec6f9c05a6

+ 16 - 0
Engine/lib/nativeFileDialogs/LICENSE

@@ -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 - 0
Engine/lib/nativeFileDialogs/README.md

@@ -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 #
+
+![Windows 8 rendering an IFileOpenDialog](screens/open_win8.png?raw=true)
+![GTK3 on Linux](screens/open_gtk3.png?raw=true)
+![Cocoa on Yosemite](screens/open_cocoa.png?raw=true)
+
+
+## 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 &copy; 2014 [Frogtoss Games](http://www.frogtoss.com), Inc.
+File [LICENSE](LICENSE) covers all files in this repo.
+
+Native File Dialog by Michael Labbe
+<[email protected]>
+
+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 - 0
Engine/lib/nativeFileDialogs/SConstruct

@@ -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 - 0
Engine/lib/nativeFileDialogs/common.h

@@ -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 - 0
Engine/lib/nativeFileDialogs/include/nfd.h

@@ -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 - 0
Engine/lib/nativeFileDialogs/nfd_cocoa.m

@@ -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 - 0
Engine/lib/nativeFileDialogs/nfd_common.c

@@ -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 - 0
Engine/lib/nativeFileDialogs/nfd_common.h

@@ -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 - 0
Engine/lib/nativeFileDialogs/nfd_gtk.c

@@ -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 - 0
Engine/lib/nativeFileDialogs/nfd_win.cpp

@@ -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;
+}

+ 721 - 0
Engine/source/platform/nativeDialogs/fileDialog.cpp

@@ -0,0 +1,721 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#include "console/simBase.h"
+#include "platform/nativeDialogs/fileDialog.h"
+#include "platform/threads/mutex.h"
+#include "core/util/safeDelete.h"
+#include "math/mMath.h"
+#include "core/strings/unicode.h"
+#include "console/consoleTypes.h"
+#include "platform/profiler.h"
+#include "console/engineAPI.h"
+#include <nfd.h>
+#include "core/strings/stringUnit.h"
+#include "core/frameAllocator.h"
+
+#if defined(TORQUE_SDL)
+//-----------------------------------------------------------------------------
+// PlatformFileDlgData Implementation
+//-----------------------------------------------------------------------------
+FileDialogData::FileDialogData()
+{
+   // Default Path
+   //
+   //  Try to provide consistent experience by recalling the last file path
+   // - else
+   //  Default to Working Directory if last path is not set or is invalid
+   mDefaultPath = StringTable->insert(Con::getVariable("Tools::FileDialogs::LastFilePath"));
+   if (mDefaultPath == StringTable->lookup("") || !Platform::isDirectory(mDefaultPath))
+      mDefaultPath = Platform::getCurrentDirectory();
+
+   mDefaultFile = StringTable->insert("");
+   mFilters = StringTable->insert("");
+   mFile = StringTable->insert("");
+   mTitle = StringTable->insert("");
+
+   mStyle = 0;
+
+}
+FileDialogData::~FileDialogData()
+{
+
+}
+
+//-----------------------------------------------------------------------------
+// FileDialog Implementation
+//-----------------------------------------------------------------------------
+IMPLEMENT_CONOBJECT(FileDialog);
+
+ConsoleDocClass(FileDialog,
+   "@brief Base class responsible for displaying an OS file browser.\n\n"
+
+   "FileDialog is a platform agnostic dialog interface for querying the user for "
+   "file locations. It is designed to be used through the exposed scripting interface.\n\n"
+
+   "FileDialog is the base class for Native File Dialog controls in Torque. It provides these basic areas of functionality:\n\n"
+   "   - Inherits from SimObject and is exposed to the scripting interface\n"
+   "   - Provides blocking interface to allow instant return to script execution\n"
+   "   - Simple object configuration makes practical use easy and effective\n\n"
+
+   "FileDialog is *NOT* intended to be used directly in script and is only exposed to script to expose generic file dialog attributes.\n\n"
+
+   "This base class is usable in TorqueScript, but is does not specify what functionality is intended (open or save?). "
+   "Its children, OpenFileDialog and SaveFileDialog, do make use of DialogStyle flags and do make use of specific funcationality. "
+   "These are the preferred classes to use\n\n"
+
+   "However, the FileDialog base class does contain the key properties and important method for file browing. The most "
+   "important function is Execute(). This is used by both SaveFileDialog and OpenFileDialog to initiate the browser.\n\n"
+
+   "@tsexample\n"
+   "// NOTE: This is not he preferred class to use, but this still works\n\n"
+   "// Create the file dialog\n"
+   "%baseFileDialog = new FileDialog()\n"
+   "{\n"
+   "   // Allow browsing of all file types\n"
+   "   filters = \"*.*\";\n\n"
+   "   // No default file\n"
+   "   defaultFile = "";\n\n"
+   "   // Set default path relative to project\n"
+   "   defaultPath = \"./\";\n\n"
+   "   // Set the title\n"
+   "   title = \"Durpa\";\n\n"
+   "   // Allow changing of path you are browsing\n"
+   "   changePath = true;\n"
+   "};\n\n"
+   " // Launch the file dialog\n"
+   " %baseFileDialog.Execute();\n"
+   " \n"
+   " // Don't forget to cleanup\n"
+   " %baseFileDialog.delete();\n\n\n"
+   "@endtsexample\n\n"
+
+   "@note FileDialog and its related classes are only availble in a Tools build of Torque.\n\n"
+
+   "@see OpenFileDialog for a practical example on opening a file\n"
+   "@see SaveFileDialog for a practical example of saving a file\n"
+
+   "@ingroup FileSystem\n"
+   );
+
+FileDialog::FileDialog() : mData()
+{
+   // Default to File Must Exist Open Dialog style
+   mData.mStyle = FileDialogData::FDS_OPEN | FileDialogData::FDS_MUSTEXIST;
+   mChangePath = false;
+}
+
+FileDialog::~FileDialog()
+{
+}
+
+void FileDialog::initPersistFields()
+{
+   addProtectedField("defaultPath", TypeString, Offset(mData.mDefaultPath, FileDialog), &setDefaultPath, &defaultProtectedGetFn,
+      "The default directory path when the dialog is shown.");
+
+   addProtectedField("defaultFile", TypeString, Offset(mData.mDefaultFile, FileDialog), &setDefaultFile, &defaultProtectedGetFn,
+      "The default file path when the dialog is shown.");
+
+   addProtectedField("fileName", TypeString, Offset(mData.mFile, FileDialog), &setFile, &defaultProtectedGetFn,
+      "The default file name when the dialog is shown.");
+
+   addProtectedField("filters", TypeString, Offset(mData.mFilters, FileDialog), &setFilters, &defaultProtectedGetFn,
+      "The filter string for limiting the types of files visible in the dialog.  It makes use of the pipe symbol '|' "
+      "as a delimiter.  For example:\n\n"
+      "'All Files|*.*'\n\n"
+      "'Image Files|*.png;*.jpg|Png Files|*.png|Jepg Files|*.jpg'");
+
+   addField("title", TypeString, Offset(mData.mTitle, FileDialog),
+      "The title for the dialog.");
+
+   addProtectedField("changePath", TypeBool, Offset(mChangePath, FileDialog), &setChangePath, &getChangePath,
+      "True/False whether to set the working directory to the directory returned by the dialog.");
+
+   Parent::initPersistFields();
+}
+
+static const U32 convertUTF16toUTF8DoubleNULL(const UTF16 *unistring, UTF8  *outbuffer, U32 len)
+{
+   AssertFatal(len >= 1, "Buffer for unicode conversion must be large enough to hold at least the null terminator.");
+   PROFILE_START(convertUTF16toUTF8DoubleNULL);
+   U32 walked, nCodeunits, codeunitLen;
+   UTF32 middleman;
+
+   nCodeunits = 0;
+   while (!(*unistring == '\0' && *(unistring + 1) == '\0') && nCodeunits + 3 < len)
+   {
+      walked = 1;
+      middleman = oneUTF16toUTF32(unistring, &walked);
+      codeunitLen = oneUTF32toUTF8(middleman, &outbuffer[nCodeunits]);
+      unistring += walked;
+      nCodeunits += codeunitLen;
+   }
+
+   nCodeunits = getMin(nCodeunits, len - 1);
+   outbuffer[nCodeunits] = '\0';
+   outbuffer[nCodeunits + 1] = '\0';
+
+   PROFILE_END();
+   return nCodeunits;
+}
+
+//
+// Execute Method
+//
+bool FileDialog::Execute()
+{
+   String suffix;
+
+   U32 filtersCount = StringUnit::getUnitCount(mData.mFilters, "|");
+
+   for (U32 i = 1; i < filtersCount; ++i)
+   {
+      //The first of each pair is the name, which we'll skip because NFD doesn't support named filters atm
+      const char *filter = StringUnit::getUnit(mData.mFilters, i, "|");
+
+      if (!dStrcmp(filter, "*.*"))
+         continue;
+
+      U32 c = 2;
+      const char* tmpchr = &filter[c];
+      String tString = String(tmpchr);
+      tString.ToLower(tString);
+      suffix += tString;
+      suffix += String(",");
+      suffix += tString.ToUpper(tString);
+
+      ++i;
+      if (i < filtersCount-2)
+         suffix += String(";");
+   }
+   String strippedFilters = suffix;
+   strippedFilters.replace(";",",");
+      strippedFilters += String(";") + suffix;
+
+   // Get the current working directory, so we can back up to it once Windows has
+   // done its craziness and messed with it.
+   StringTableEntry cwd = Platform::getCurrentDirectory();
+   if (mData.mDefaultPath == StringTable->lookup("") || !Platform::isDirectory(mData.mDefaultPath))
+      mData.mDefaultPath = cwd;
+
+   // Execute Dialog (Blocking Call)
+   nfdchar_t *outPath = NULL;
+   nfdpathset_t pathSet;
+
+   nfdresult_t result = NFD_ERROR;
+   String defaultPath = String(mData.mDefaultPath);
+#if defined(TORQUE_OS_WIN)
+   defaultPath.replace("/", "\\");
+#endif
+
+   if (mData.mStyle & FileDialogData::FDS_OPEN)
+      result = NFD_OpenDialog(strippedFilters.c_str(), defaultPath.c_str(), &outPath);
+   else if (mData.mStyle & FileDialogData::FDS_SAVE)
+      result = NFD_SaveDialog(strippedFilters.c_str(), defaultPath.c_str(), &outPath);
+   else if (mData.mStyle & FileDialogData::FDS_MULTIPLEFILES)
+      result = NFD_OpenDialogMultiple(strippedFilters.c_str(), defaultPath.c_str(), &pathSet);
+
+   // Did we select a file?
+   if (result != NFD_OKAY)
+   {
+      Con::errorf("NFD plugin error: %s", NFD_GetError());
+      return false;
+   }
+   // Store the result on our object
+   if (mData.mStyle & FileDialogData::FDS_OPEN || mData.mStyle & FileDialogData::FDS_SAVE)
+   {
+      // Single file selection, do it the easy way
+      mData.mFile = StringTable->insert(outPath);
+   }
+   else if (mData.mStyle & FileDialogData::FDS_MULTIPLEFILES)
+   {
+      //check if we have multiple files actually selected or not
+      U32 fileCount = NFD_PathSet_GetCount(&pathSet);
+      if (fileCount > 1)
+      {
+         //yep, so parse through them and prep our return
+         for (U32 i = 0; i < fileCount; ++i)
+         {
+            nfdchar_t *path = NFD_PathSet_GetPath(&pathSet, i);
+            setDataField(StringTable->insert("files"), Con::getIntArg(i), path);
+         }
+
+         setDataField(StringTable->insert("fileCount"), NULL, Con::getIntArg(fileCount));
+      }
+      else
+      {
+         //nope, just one file, so set it as normal
+         setDataField(StringTable->insert("files"), "0", outPath);
+         setDataField(StringTable->insert("fileCount"), NULL, "1");
+      }
+   }
+
+   // Return success.
+   return true;
+
+}
+
+DefineEngineMethod(FileDialog, Execute, bool, (), ,
+   "@brief Launches the OS file browser\n\n"
+
+   "After an Execute() call, the chosen file name and path is available in one of two areas.  "
+   "If only a single file selection is permitted, the results will be stored in the @a fileName "
+   "attribute.\n\n"
+
+   "If multiple file selection is permitted, the results will be stored in the "
+   "@a files array.  The total number of files in the array will be stored in the "
+   "@a fileCount attribute.\n\n"
+
+   "@tsexample\n"
+   "// NOTE: This is not he preferred class to use, but this still works\n\n"
+   "// Create the file dialog\n"
+   "%baseFileDialog = new FileDialog()\n"
+   "{\n"
+   "   // Allow browsing of all file types\n"
+   "   filters = \"*.*\";\n\n"
+   "   // No default file\n"
+   "   defaultFile = "";\n\n"
+   "   // Set default path relative to project\n"
+   "   defaultPath = \"./\";\n\n"
+   "   // Set the title\n"
+   "   title = \"Durpa\";\n\n"
+   "   // Allow changing of path you are browsing\n"
+   "   changePath = true;\n"
+   "};\n\n"
+   " // Launch the file dialog\n"
+   " %baseFileDialog.Execute();\n"
+   " \n"
+   " // Don't forget to cleanup\n"
+   " %baseFileDialog.delete();\n\n\n"
+
+   " // A better alternative is to use the \n"
+   " // derived classes which are specific to file open and save\n\n"
+   " // Create a dialog dedicated to opening files\n"
+   " %openFileDlg = new OpenFileDialog()\n"
+   " {\n"
+   "    // Look for jpg image files\n"
+   "    // First part is the descriptor|second part is the extension\n"
+   "    Filters = \"Jepg Files|*.jpg\";\n"
+   "    // Allow browsing through other folders\n"
+   "    ChangePath = true;\n\n"
+   "    // Only allow opening of one file at a time\n"
+   "    MultipleFiles = false;\n"
+   " };\n\n"
+   " // Launch the open file dialog\n"
+   " %result = %openFileDlg.Execute();\n\n"
+   " // Obtain the chosen file name and path\n"
+   " if ( %result )\n"
+   " {\n"
+   "    %seletedFile = %openFileDlg.file;\n"
+   " }\n"
+   " else\n"
+   " {\n"
+   "    %selectedFile = \"\";\n"
+   " }\n"
+   " // Cleanup\n"
+   " %openFileDlg.delete();\n\n\n"
+
+   " // Create a dialog dedicated to saving a file\n"
+   " %saveFileDlg = new SaveFileDialog()\n"
+   " {\n"
+   "    // Only allow for saving of COLLADA files\n"
+   "    Filters = \"COLLADA Files (*.dae)|*.dae|\";\n\n"
+   "    // Default save path to where the WorldEditor last saved\n"
+   "    DefaultPath = $pref::WorldEditor::LastPath;\n\n"
+   "    // No default file specified\n"
+   "    DefaultFile = \"\";\n\n"
+   "    // Do not allow the user to change to a new directory\n"
+   "    ChangePath = false;\n\n"
+   "    // Prompt the user if they are going to overwrite an existing file\n"
+   "    OverwritePrompt = true;\n"
+   " };\n\n"
+   " // Launch the save file dialog\n"
+   " %result = %saveFileDlg.Execute();\n\n"
+   " // Obtain the file name\n"
+   " %selectedFile = \"\";\n"
+   " if ( %result )\n"
+   "    %selectedFile = %saveFileDlg.file;\n\n"
+   " // Cleanup\n"
+   " %saveFileDlg.delete();\n"
+   "@endtsexample\n\n"
+
+   "@return True if the file was selected was successfully found (opened) or declared (saved).")
+{
+   return object->Execute();
+}
+
+//-----------------------------------------------------------------------------
+// Dialog Filters
+//-----------------------------------------------------------------------------
+bool FileDialog::setFilters(void *object, const char *index, const char *data)
+{
+   // Will do validate on write at some point.
+   if (!data)
+      return true;
+
+   return true;
+
+};
+
+
+//-----------------------------------------------------------------------------
+// Default Path Property - String Validated on Write
+//-----------------------------------------------------------------------------
+bool FileDialog::setDefaultPath(void *object, const char *index, const char *data)
+{
+   if (!data || !dStrncmp(data, "", 1))
+      return true;
+
+   // Copy and Backslash the path (Windows dialogs are VERY picky about this format)
+   static char szPathValidate[512];
+   dStrcpy(szPathValidate, data);
+
+   Platform::makeFullPathName(data, szPathValidate, sizeof(szPathValidate));
+   //backslash( szPathValidate );
+
+   // Remove any trailing \'s
+   S8 validateLen = dStrlen(szPathValidate);
+   if (szPathValidate[validateLen - 1] == '\\')
+      szPathValidate[validateLen - 1] = '\0';
+
+   // Now check
+   if (Platform::isDirectory(szPathValidate))
+   {
+      // Finally, assign in proper format.
+      FileDialog *pDlg = static_cast<FileDialog*>(object);
+      pDlg->mData.mDefaultPath = StringTable->insert(szPathValidate);
+   }
+#ifdef TORQUE_DEBUG
+   else
+      Con::errorf(ConsoleLogEntry::GUI, "FileDialog - Invalid Default Path Specified!");
+#endif
+
+   return false;
+
+};
+
+//-----------------------------------------------------------------------------
+// Default File Property - String Validated on Write
+//-----------------------------------------------------------------------------
+bool FileDialog::setDefaultFile(void *object, const char *index, const char *data)
+{
+   if (!data || !dStrncmp(data, "", 1))
+      return true;
+
+   // Copy and Backslash the path (Windows dialogs are VERY picky about this format)
+   static char szPathValidate[512];
+   Platform::makeFullPathName(data, szPathValidate, sizeof(szPathValidate));
+   //backslash( szPathValidate );
+
+   // Remove any trailing \'s
+   S8 validateLen = dStrlen(szPathValidate);
+   if (szPathValidate[validateLen - 1] == '\\')
+      szPathValidate[validateLen - 1] = '\0';
+
+   // Finally, assign in proper format.
+   FileDialog *pDlg = static_cast<FileDialog*>(object);
+   pDlg->mData.mDefaultFile = StringTable->insert(szPathValidate);
+
+   return false;
+};
+
+//-----------------------------------------------------------------------------
+// ChangePath Property - Change working path on successful file selection
+//-----------------------------------------------------------------------------
+bool FileDialog::setChangePath(void *object, const char *index, const char *data)
+{
+   bool bMustExist = dAtob(data);
+
+   FileDialog *pDlg = static_cast<FileDialog*>(object);
+
+   if (bMustExist)
+      pDlg->mData.mStyle |= FileDialogData::FDS_CHANGEPATH;
+   else
+      pDlg->mData.mStyle &= ~FileDialogData::FDS_CHANGEPATH;
+
+   return true;
+};
+
+const char* FileDialog::getChangePath(void* obj, const char* data)
+{
+   FileDialog *pDlg = static_cast<FileDialog*>(obj);
+   if (pDlg->mData.mStyle & FileDialogData::FDS_CHANGEPATH)
+      return StringTable->insert("true");
+   else
+      return StringTable->insert("false");
+}
+
+bool FileDialog::setFile(void *object, const char *index, const char *data)
+{
+   return false;
+};
+
+//-----------------------------------------------------------------------------
+// OpenFileDialog Implementation
+//-----------------------------------------------------------------------------
+
+ConsoleDocClass(OpenFileDialog,
+   "@brief Derived from FileDialog, this class is responsible for opening a file browser with the intention of opening a file.\n\n"
+
+   "The core usage of this dialog is to locate a file in the OS and return the path and name. This does not handle "
+   "the actual file parsing or data manipulation. That functionality is left up to the FileObject class.\n\n"
+
+   "@tsexample\n"
+   " // Create a dialog dedicated to opening files\n"
+   " %openFileDlg = new OpenFileDialog()\n"
+   " {\n"
+   "    // Look for jpg image files\n"
+   "    // First part is the descriptor|second part is the extension\n"
+   "    Filters = \"Jepg Files|*.jpg\";\n"
+   "    // Allow browsing through other folders\n"
+   "    ChangePath = true;\n\n"
+   "    // Only allow opening of one file at a time\n"
+   "    MultipleFiles = false;\n"
+   " };\n\n"
+   " // Launch the open file dialog\n"
+   " %result = %openFileDlg.Execute();\n\n"
+   " // Obtain the chosen file name and path\n"
+   " if ( %result )\n"
+   " {\n"
+   "    %seletedFile = %openFileDlg.file;\n"
+   " }\n"
+   " else\n"
+   " {\n"
+   "    %selectedFile = \"\";\n"
+   " }\n\n"
+   " // Cleanup\n"
+   " %openFileDlg.delete();\n\n\n"
+   "@endtsexample\n\n"
+
+   "@note FileDialog and its related classes are only availble in a Tools build of Torque.\n\n"
+
+   "@see FileDialog\n"
+   "@see SaveFileDialog\n"
+   "@see FileObject\n"
+
+   "@ingroup FileSystem\n"
+   );
+OpenFileDialog::OpenFileDialog()
+{
+   // Default File Must Exist
+   mData.mStyle = FileDialogData::FDS_OPEN | FileDialogData::FDS_MUSTEXIST;
+}
+
+OpenFileDialog::~OpenFileDialog()
+{
+   mMustExist = true;
+   mMultipleFiles = false;
+}
+
+IMPLEMENT_CONOBJECT(OpenFileDialog);
+
+//-----------------------------------------------------------------------------
+// Console Properties
+//-----------------------------------------------------------------------------
+void OpenFileDialog::initPersistFields()
+{
+   addProtectedField("MustExist", TypeBool, Offset(mMustExist, OpenFileDialog), &setMustExist, &getMustExist, "True/False whether the file returned must exist or not");
+   addProtectedField("MultipleFiles", TypeBool, Offset(mMultipleFiles, OpenFileDialog), &setMultipleFiles, &getMultipleFiles, "True/False whether multiple files may be selected and returned or not");
+
+   Parent::initPersistFields();
+}
+
+//-----------------------------------------------------------------------------
+// File Must Exist - Boolean
+//-----------------------------------------------------------------------------
+bool OpenFileDialog::setMustExist(void *object, const char *index, const char *data)
+{
+   bool bMustExist = dAtob(data);
+
+   OpenFileDialog *pDlg = static_cast<OpenFileDialog*>(object);
+
+   if (bMustExist)
+      pDlg->mData.mStyle |= FileDialogData::FDS_MUSTEXIST;
+   else
+      pDlg->mData.mStyle &= ~FileDialogData::FDS_MUSTEXIST;
+
+   return true;
+};
+
+const char* OpenFileDialog::getMustExist(void* obj, const char* data)
+{
+   OpenFileDialog *pDlg = static_cast<OpenFileDialog*>(obj);
+   if (pDlg->mData.mStyle & FileDialogData::FDS_MUSTEXIST)
+      return StringTable->insert("true");
+   else
+      return StringTable->insert("false");
+}
+
+//-----------------------------------------------------------------------------
+// Can Select Multiple Files - Boolean
+//-----------------------------------------------------------------------------
+bool OpenFileDialog::setMultipleFiles(void *object, const char *index, const char *data)
+{
+   bool bMustExist = dAtob(data);
+
+   OpenFileDialog *pDlg = static_cast<OpenFileDialog*>(object);
+
+   if (bMustExist)
+      pDlg->mData.mStyle |= FileDialogData::FDS_MULTIPLEFILES;
+   else
+      pDlg->mData.mStyle &= ~FileDialogData::FDS_MULTIPLEFILES;
+
+   return true;
+};
+
+const char* OpenFileDialog::getMultipleFiles(void* obj, const char* data)
+{
+   OpenFileDialog *pDlg = static_cast<OpenFileDialog*>(obj);
+   if (pDlg->mData.mStyle & FileDialogData::FDS_MULTIPLEFILES)
+      return StringTable->insert("true");
+   else
+      return StringTable->insert("false");
+}
+
+//-----------------------------------------------------------------------------
+// SaveFileDialog Implementation
+//-----------------------------------------------------------------------------
+ConsoleDocClass(SaveFileDialog,
+   "@brief Derived from FileDialog, this class is responsible for opening a file browser with the intention of saving a file.\n\n"
+
+   "The core usage of this dialog is to locate a file in the OS and return the path and name. This does not handle "
+   "the actual file writing or data manipulation. That functionality is left up to the FileObject class.\n\n"
+
+   "@tsexample\n"
+   " // Create a dialog dedicated to opening file\n"
+   " %saveFileDlg = new SaveFileDialog()\n"
+   " {\n"
+   "    // Only allow for saving of COLLADA files\n"
+   "    Filters        = \"COLLADA Files (*.dae)|*.dae|\";\n\n"
+   "    // Default save path to where the WorldEditor last saved\n"
+   "    DefaultPath    = $pref::WorldEditor::LastPath;\n\n"
+   "    // No default file specified\n"
+   "    DefaultFile    = \"\";\n\n"
+   "    // Do not allow the user to change to a new directory\n"
+   "    ChangePath     = false;\n\n"
+   "    // Prompt the user if they are going to overwrite an existing file\n"
+   "    OverwritePrompt   = true;\n"
+   " };\n\n"
+   " // Launch the save file dialog\n"
+   " %saveFileDlg.Execute();\n\n"
+   " if ( %result )\n"
+   " {\n"
+   "    %seletedFile = %openFileDlg.file;\n"
+   " }\n"
+   " else\n"
+   " {\n"
+   "    %selectedFile = \"\";\n"
+   " }\n\n"
+   " // Cleanup\n"
+   " %saveFileDlg.delete();\n"
+   "@endtsexample\n\n"
+
+   "@note FileDialog and its related classes are only availble in a Tools build of Torque.\n\n"
+
+   "@see FileDialog\n"
+   "@see OpenFileDialog\n"
+   "@see FileObject\n"
+
+   "@ingroup FileSystem\n"
+   );
+SaveFileDialog::SaveFileDialog()
+{
+   // Default File Must Exist
+   mData.mStyle = FileDialogData::FDS_SAVE | FileDialogData::FDS_OVERWRITEPROMPT;
+   mOverwritePrompt = true;
+}
+
+SaveFileDialog::~SaveFileDialog()
+{
+}
+
+IMPLEMENT_CONOBJECT(SaveFileDialog);
+
+//-----------------------------------------------------------------------------
+// Console Properties
+//-----------------------------------------------------------------------------
+void SaveFileDialog::initPersistFields()
+{
+   addProtectedField("OverwritePrompt", TypeBool, Offset(mOverwritePrompt, SaveFileDialog), &setOverwritePrompt, &getOverwritePrompt, "True/False whether the dialog should prompt before accepting an existing file name");
+
+   Parent::initPersistFields();
+}
+
+//-----------------------------------------------------------------------------
+// Prompt on Overwrite - Boolean
+//-----------------------------------------------------------------------------
+bool SaveFileDialog::setOverwritePrompt(void *object, const char *index, const char *data)
+{
+   bool bMustExist = dAtob(data);
+
+   SaveFileDialog *pDlg = static_cast<SaveFileDialog*>(object);
+
+   if (bMustExist)
+      pDlg->mData.mStyle |= FileDialogData::FDS_OVERWRITEPROMPT;
+   else
+      pDlg->mData.mStyle &= ~FileDialogData::FDS_OVERWRITEPROMPT;
+
+   return true;
+};
+
+const char* SaveFileDialog::getOverwritePrompt(void* obj, const char* data)
+{
+   SaveFileDialog *pDlg = static_cast<SaveFileDialog*>(obj);
+   if (pDlg->mData.mStyle & FileDialogData::FDS_OVERWRITEPROMPT)
+      return StringTable->insert("true");
+   else
+      return StringTable->insert("false");
+}
+
+//-----------------------------------------------------------------------------
+// OpenFolderDialog Implementation
+//-----------------------------------------------------------------------------
+
+OpenFolderDialog::OpenFolderDialog()
+{
+   mData.mStyle = FileDialogData::FDS_OPEN | FileDialogData::FDS_OVERWRITEPROMPT | FileDialogData::FDS_BROWSEFOLDER;
+
+   mMustExistInDir = "";
+}
+
+IMPLEMENT_CONOBJECT(OpenFolderDialog);
+
+ConsoleDocClass(OpenFolderDialog,
+   "@brief OS level dialog used for browsing folder structures.\n\n"
+
+   "This is essentially an OpenFileDialog, but only used for returning directory paths, not files.\n\n"
+
+   "@note FileDialog and its related classes are only availble in a Tools build of Torque.\n\n"
+
+   "@see OpenFileDialog for more details on functionality.\n\n"
+
+   "@ingroup FileSystem\n"
+   );
+
+void OpenFolderDialog::initPersistFields()
+{
+   addField("fileMustExist", TypeFilename, Offset(mMustExistInDir, OpenFolderDialog), "File that must be in selected folder for it to be valid");
+
+   Parent::initPersistFields();
+}
+#endif

+ 0 - 354
Engine/source/platformX86UNIX/nativeDialogs/fileDialog.cpp

@@ -1,354 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2012 GarageGames, LLC
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to
-// deal in the Software without restriction, including without limitation the
-// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-// sell copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//-----------------------------------------------------------------------------
-
-#include "platform/nativeDialogs/fileDialog.h"
-
-#ifdef TORQUE_TOOLS
-//-----------------------------------------------------------------------------
-// PlatformFileDlgData Implementation
-//-----------------------------------------------------------------------------
-FileDialogData::FileDialogData()
-{
-    AssertFatal(0, "Not Implemented");
-}
-
-FileDialogData::~FileDialogData()
-{
-}
-
-//-----------------------------------------------------------------------------
-// FileDialog Implementation
-//-----------------------------------------------------------------------------
-IMPLEMENT_CONOBJECT(FileDialog);
-
-ConsoleDocClass( FileDialog,
-   "@brief Base class responsible for displaying an OS file browser.\n\n"
-
-   "FileDialog is a platform agnostic dialog interface for querying the user for "
-   "file locations. It is designed to be used through the exposed scripting interface.\n\n"
-   
-   "FileDialog is the base class for Native File Dialog controls in Torque. It provides these basic areas of functionality:\n\n"
-   "   - Inherits from SimObject and is exposed to the scripting interface\n"
-   "   - Provides blocking interface to allow instant return to script execution\n"
-   "   - Simple object configuration makes practical use easy and effective\n\n"
-   
-   "FileDialog is *NOT* intended to be used directly in script and is only exposed to script to expose generic file dialog attributes.\n\n"
-
-   "This base class is usable in TorqueScript, but is does not specify what functionality is intended (open or save?). "
-   "Its children, OpenFileDialog and SaveFileDialog, do make use of DialogStyle flags and do make use of specific funcationality. "
-   "These are the preferred classes to use\n\n"
-
-   "However, the FileDialog base class does contain the key properties and important method for file browing. The most "
-   "important function is Execute(). This is used by both SaveFileDialog and OpenFileDialog to initiate the browser.\n\n"
-
-   "@tsexample\n"
-   "// NOTE: This is not he preferred class to use, but this still works\n\n"
-   "// Create the file dialog\n"
-   "%baseFileDialog = new FileDialog()\n"
-   "{\n"
-   "   // Allow browsing of all file types\n"
-   "   filters = \"*.*\";\n\n"
-   "   // No default file\n"
-   "   defaultFile = "";\n\n"
-   "   // Set default path relative to project\n"
-   "   defaultPath = \"./\";\n\n"
-   "   // Set the title\n"
-   "   title = \"Durpa\";\n\n"
-   "   // Allow changing of path you are browsing\n"
-   "   changePath = true;\n"
-   "};\n\n"
-   " // Launch the file dialog\n"
-   " %baseFileDialog.Execute();\n"
-   " \n"
-   " // Don't forget to cleanup\n"
-   " %baseFileDialog.delete();\n\n\n"
-   "@endtsexample\n\n"
-
-   "@note FileDialog and its related classes are only availble in a Tools build of Torque.\n\n"
-
-   "@see OpenFileDialog for a practical example on opening a file\n"
-   "@see SaveFileDialog for a practical example of saving a file\n"
-
-   "@ingroup FileSystem\n"
-);
-
-FileDialog::FileDialog() : mData()
-{
-    AssertFatal(0, "Not Implemented");
-}
-
-FileDialog::~FileDialog()
-{
-}
-
-void FileDialog::initPersistFields()
-{
-    Parent::initPersistFields();
-}
-
-//
-// Execute Method
-//
-bool FileDialog::Execute()
-{
-    return false;
-}
-
-//-----------------------------------------------------------------------------
-// Dialog Filters
-//-----------------------------------------------------------------------------
-bool FileDialog::setFilters( void *object, const char *index, const char *data )
-{
-   return true;
-};
-
-
-//-----------------------------------------------------------------------------
-// Default Path Property - String Validated on Write
-//-----------------------------------------------------------------------------
-bool FileDialog::setDefaultPath( void *object, const char *index, const char *data )
-{
-   return false;
-};
-
-//-----------------------------------------------------------------------------
-// Default File Property - String Validated on Write
-//-----------------------------------------------------------------------------
-bool FileDialog::setDefaultFile( void *object, const char *index, const char *data )
-{
-    return false;
-};
-
-//-----------------------------------------------------------------------------
-// ChangePath Property - Change working path on successful file selection
-//-----------------------------------------------------------------------------
-bool FileDialog::setChangePath( void *object, const char *index, const char *data )
-{
-    return true;
-};
-
-const char* FileDialog::getChangePath(void* obj, const char* data)
-{
-    return 0;
-}
-
-bool FileDialog::setFile( void *object, const char *index, const char *data )
-{
-    return false;
-};
-
-//-----------------------------------------------------------------------------
-// OpenFileDialog Implementation
-//-----------------------------------------------------------------------------
-
-ConsoleDocClass( OpenFileDialog,
-   "@brief Derived from FileDialog, this class is responsible for opening a file browser with the intention of opening a file.\n\n"
-
-   "The core usage of this dialog is to locate a file in the OS and return the path and name. This does not handle "
-   "the actual file parsing or data manipulation. That functionality is left up to the FileObject class.\n\n"
-   
-   "@tsexample\n"
-   " // Create a dialog dedicated to opening files\n"
-   " %openFileDlg = new OpenFileDialog()\n"
-   " {\n"
-   "    // Look for jpg image files\n"
-   "    // First part is the descriptor|second part is the extension\n"
-   "    Filters = \"Jepg Files|*.jpg\";\n"
-   "    // Allow browsing through other folders\n"
-   "    ChangePath = true;\n\n"
-   "    // Only allow opening of one file at a time\n"
-   "    MultipleFiles = false;\n"
-   " };\n\n"
-   " // Launch the open file dialog\n"
-   " %result = %openFileDlg.Execute();\n\n"
-   " // Obtain the chosen file name and path\n"
-   " if ( %result )\n"
-   " {\n"
-   "    %seletedFile = %openFileDlg.file;\n"
-   " }\n"
-   " else\n"
-   " {\n"
-   "    %selectedFile = \"\";\n"
-   " }\n\n"
-   " // Cleanup\n"
-   " %openFileDlg.delete();\n\n\n"
-   "@endtsexample\n\n"
-
-   "@note FileDialog and its related classes are only availble in a Tools build of Torque.\n\n"
-
-   "@see FileDialog\n"
-   "@see SaveFileDialog\n"
-   "@see FileObject\n"
-
-   "@ingroup FileSystem\n"
-);
-OpenFileDialog::OpenFileDialog()
-{
-    AssertFatal(0, "Not Implemented");
-}
-
-OpenFileDialog::~OpenFileDialog()
-{
-}
-
-IMPLEMENT_CONOBJECT(OpenFileDialog);
-
-//-----------------------------------------------------------------------------
-// Console Properties
-//-----------------------------------------------------------------------------
-void OpenFileDialog::initPersistFields()
-{
-}
-
-//-----------------------------------------------------------------------------
-// File Must Exist - Boolean
-//-----------------------------------------------------------------------------
-bool OpenFileDialog::setMustExist( void *object, const char *index, const char *data )
-{
-    return true;
-};
-
-const char* OpenFileDialog::getMustExist(void* obj, const char* data)
-{
-    return 0;
-}
-
-//-----------------------------------------------------------------------------
-// Can Select Multiple Files - Boolean
-//-----------------------------------------------------------------------------
-bool OpenFileDialog::setMultipleFiles( void *object, const char *index, const char *data )
-{
-   return true;
-};
-
-const char* OpenFileDialog::getMultipleFiles(void* obj, const char* data)
-{
-    return 0;
-}
-
-//-----------------------------------------------------------------------------
-// SaveFileDialog Implementation
-//-----------------------------------------------------------------------------
-ConsoleDocClass( SaveFileDialog,
-   "@brief Derived from FileDialog, this class is responsible for opening a file browser with the intention of saving a file.\n\n"
-
-   "The core usage of this dialog is to locate a file in the OS and return the path and name. This does not handle "
-   "the actual file writing or data manipulation. That functionality is left up to the FileObject class.\n\n"
-   
-   "@tsexample\n"
-   " // Create a dialog dedicated to opening file\n"
-   " %saveFileDlg = new SaveFileDialog()\n"
-   " {\n"
-   "    // Only allow for saving of COLLADA files\n"
-   "    Filters        = \"COLLADA Files (*.dae)|*.dae|\";\n\n"
-   "    // Default save path to where the WorldEditor last saved\n"
-   "    DefaultPath    = $pref::WorldEditor::LastPath;\n\n"
-   "    // No default file specified\n"
-   "    DefaultFile    = \"\";\n\n"
-   "    // Do not allow the user to change to a new directory\n"
-   "    ChangePath     = false;\n\n"
-   "    // Prompt the user if they are going to overwrite an existing file\n"
-   "    OverwritePrompt   = true;\n"
-   " };\n\n"
-   " // Launch the save file dialog\n"
-   " %saveFileDlg.Execute();\n\n"
-   " if ( %result )\n"
-   " {\n"
-   "    %seletedFile = %openFileDlg.file;\n"
-   " }\n"
-   " else\n"
-   " {\n"
-   "    %selectedFile = \"\";\n"
-   " }\n\n"
-   " // Cleanup\n"
-   " %saveFileDlg.delete();\n"
-   "@endtsexample\n\n"
-
-   "@note FileDialog and its related classes are only availble in a Tools build of Torque.\n\n"
-
-   "@see FileDialog\n"
-   "@see OpenFileDialog\n"
-   "@see FileObject\n"
-
-   "@ingroup FileSystem\n"
-);
-SaveFileDialog::SaveFileDialog()
-{
-    AssertFatal(0, "Not Implemented");
-}
-
-SaveFileDialog::~SaveFileDialog()
-{
-}
-
-IMPLEMENT_CONOBJECT(SaveFileDialog);
-
-//-----------------------------------------------------------------------------
-// Console Properties
-//-----------------------------------------------------------------------------
-void SaveFileDialog::initPersistFields()
-{
-    Parent::initPersistFields();
-}
-
-//-----------------------------------------------------------------------------
-// Prompt on Overwrite - Boolean
-//-----------------------------------------------------------------------------
-bool SaveFileDialog::setOverwritePrompt( void *object, const char *index, const char *data )
-{
-    return true;
-};
-
-const char* SaveFileDialog::getOverwritePrompt(void* obj, const char* data)
-{
-    return 0;
-}
-
-//-----------------------------------------------------------------------------
-// OpenFolderDialog Implementation
-//-----------------------------------------------------------------------------
-
-OpenFolderDialog::OpenFolderDialog()
-{    
-    AssertFatal(0, "Not Implemented");
-}
-
-IMPLEMENT_CONOBJECT(OpenFolderDialog);
-
-ConsoleDocClass( OpenFolderDialog,
-   "@brief OS level dialog used for browsing folder structures.\n\n"
-
-   "This is essentially an OpenFileDialog, but only used for returning directory paths, not files.\n\n"
-
-   "@note FileDialog and its related classes are only availble in a Tools build of Torque.\n\n"
-
-   "@see OpenFileDialog for more details on functionality.\n\n"
-
-   "@ingroup FileSystem\n"
-);
-
-void OpenFolderDialog::initPersistFields()
-{
-    Parent::initPersistFields();
-}
-
-#endif

+ 3 - 0
Tools/CMake/basics.cmake

@@ -71,6 +71,9 @@ macro(addPath dir)
              ${dir}/*.h
              #${dir}/*.asm
              )
+    foreach(entry ${BLACKLIST})
+ 		list(REMOVE_ITEM tmp_files ${dir}/${entry})
+ 	endforeach()
     LIST(APPEND ${PROJECT_NAME}_files "${tmp_files}")
     LIST(APPEND ${PROJECT_NAME}_paths "${dir}")
     #message(STATUS "addPath ${PROJECT_NAME} : ${tmp_files}")

+ 29 - 0
Tools/CMake/libraries/nativeFileDialogs.cmake

@@ -0,0 +1,29 @@
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014 GarageGames, LLC
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+# -----------------------------------------------------------------------------
+
+project(nativeFileDialogs)
+
+addPath("${libDir}/nativeFileDialogs" REC)
+
+addInclude(${libDir}/nativeFileDialogs/include)
+
+finishLibrary()

+ 35 - 0
Tools/CMake/torque3d.cmake

@@ -195,7 +195,13 @@ addPath("${srcDir}/windowManager/test")
 addPath("${srcDir}/math")
 addPath("${srcDir}/math/util")
 addPath("${srcDir}/math/test")
+
+if(NOT TORQUE_SDL) 
+   set(BLACKLIST "fileDialog.cpp" )
+endif()
 addPath("${srcDir}/platform")
+set(BLACKLIST "" )
+
 addPath("${srcDir}/cinterface")
 addPath("${srcDir}/platform/nativeDialogs")
 if( NOT TORQUE_DEDICATED )
@@ -365,6 +371,28 @@ if(TORQUE_SDL)
        else()
          set(ENV{LDFLAGS} "${CXX_FLAG32} ${TORQUE_ADDITIONAL_LINKER_FLAGS}")
        endif()
+
+       find_package(PkgConfig REQUIRED)
+       pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
+
+       # Setup CMake to use GTK+, tell the compiler where to look for headers
+       # and to the linker where to look for libraries
+       include_directories(${GTK3_INCLUDE_DIRS})
+       link_directories(${GTK3_LIBRARY_DIRS})
+
+       # Add other flags to the compiler
+       add_definitions(${GTK3_CFLAGS_OTHER})
+
+       set(BLACKLIST "nfd_win.cpp"  )
+       addLib(nativeFileDialogs)
+
+       set(BLACKLIST ""  )
+       target_link_libraries(nativeFileDialogs ${GTK3_LIBRARIES})
+ 	else()
+ 	   set(BLACKLIST "nfd_gtk.c" )
+ 	   addLib(nativeFileDialogs)
+       set(BLACKLIST ""  )
+ 	   addLib(comctl32)	   
     endif()
     
     #override and hide SDL2 cache variables
@@ -388,7 +416,11 @@ endforeach()
 ###############################################################################
 if(WIN32)
     addPath("${srcDir}/platformWin32")
+    if(TORQUE_SDL) 
+ 		set(BLACKLIST "fileDialog.cpp" )
+ 	endif()
     addPath("${srcDir}/platformWin32/nativeDialogs")
+    set(BLACKLIST "" )
     addPath("${srcDir}/platformWin32/menus")
     addPath("${srcDir}/platformWin32/threads")
     addPath("${srcDir}/platformWin32/videoInfo")
@@ -634,6 +666,9 @@ addInclude("${libDir}/libogg/include")
 addInclude("${libDir}/opcode")
 addInclude("${libDir}/collada/include")
 addInclude("${libDir}/collada/include/1.4")
+if(TORQUE_SDL)
+   addInclude("${libDir}/nativeFileDialogs/include")
+endif()
 if(TORQUE_OPENGL)
 	addInclude("${libDir}/glew/include")
 endif()