filedialog 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/gempush.yml +31 -0
  3. data/.gitignore +13 -0
  4. data/.gitmodules +3 -0
  5. data/.rubocop.yml +19 -0
  6. data/Gemfile +10 -0
  7. data/Gemfile.lock +30 -0
  8. data/LICENSE +674 -0
  9. data/README.md +22 -0
  10. data/Rakefile +35 -0
  11. data/bin/console +25 -0
  12. data/deps/filedialogbuilddeps.rb +49 -0
  13. data/deps/nativefiledialog/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
  14. data/deps/nativefiledialog/.gitignore +181 -0
  15. data/deps/nativefiledialog/LICENSE +16 -0
  16. data/deps/nativefiledialog/README.md +180 -0
  17. data/deps/nativefiledialog/build/dont_run_premake.txt +1 -0
  18. data/deps/nativefiledialog/build/gmake_linux/Makefile +101 -0
  19. data/deps/nativefiledialog/build/gmake_linux/nfd.make +192 -0
  20. data/deps/nativefiledialog/build/gmake_linux/test_opendialog.make +188 -0
  21. data/deps/nativefiledialog/build/gmake_linux/test_opendialogmultiple.make +188 -0
  22. data/deps/nativefiledialog/build/gmake_linux/test_pickfolder.make +188 -0
  23. data/deps/nativefiledialog/build/gmake_linux/test_savedialog.make +188 -0
  24. data/deps/nativefiledialog/build/gmake_linux_zenity/Makefile +101 -0
  25. data/deps/nativefiledialog/build/gmake_linux_zenity/nfd.make +192 -0
  26. data/deps/nativefiledialog/build/gmake_linux_zenity/test_opendialog.make +188 -0
  27. data/deps/nativefiledialog/build/gmake_linux_zenity/test_opendialogmultiple.make +188 -0
  28. data/deps/nativefiledialog/build/gmake_linux_zenity/test_pickfolder.make +188 -0
  29. data/deps/nativefiledialog/build/gmake_linux_zenity/test_savedialog.make +188 -0
  30. data/deps/nativefiledialog/build/gmake_macosx/Makefile +85 -0
  31. data/deps/nativefiledialog/build/gmake_macosx/nfd.make +154 -0
  32. data/deps/nativefiledialog/build/gmake_macosx/test_opendialog.make +150 -0
  33. data/deps/nativefiledialog/build/gmake_macosx/test_opendialogmultiple.make +150 -0
  34. data/deps/nativefiledialog/build/gmake_macosx/test_pickfolder.make +150 -0
  35. data/deps/nativefiledialog/build/gmake_macosx/test_savedialog.make +150 -0
  36. data/deps/nativefiledialog/build/gmake_windows/Makefile +101 -0
  37. data/deps/nativefiledialog/build/gmake_windows/nfd.make +192 -0
  38. data/deps/nativefiledialog/build/gmake_windows/test_opendialog.make +188 -0
  39. data/deps/nativefiledialog/build/gmake_windows/test_opendialogmultiple.make +188 -0
  40. data/deps/nativefiledialog/build/gmake_windows/test_pickfolder.make +188 -0
  41. data/deps/nativefiledialog/build/gmake_windows/test_savedialog.make +188 -0
  42. data/deps/nativefiledialog/build/premake5.lua +265 -0
  43. data/deps/nativefiledialog/build/vs2010/NativeFileDialog.sln +78 -0
  44. data/deps/nativefiledialog/build/vs2010/nfd.vcxproj +168 -0
  45. data/deps/nativefiledialog/build/vs2010/nfd.vcxproj.filters +20 -0
  46. data/deps/nativefiledialog/build/vs2010/test_opendialog.vcxproj +182 -0
  47. data/deps/nativefiledialog/build/vs2010/test_opendialogmultiple.vcxproj +182 -0
  48. data/deps/nativefiledialog/build/vs2010/test_pickfolder.vcxproj +182 -0
  49. data/deps/nativefiledialog/build/vs2010/test_savedialog.vcxproj +182 -0
  50. data/deps/nativefiledialog/build/xcode4/NativeFileDialog.xcworkspace/contents.xcworkspacedata +19 -0
  51. data/deps/nativefiledialog/build/xcode4/nfd.xcodeproj/project.pbxproj +228 -0
  52. data/deps/nativefiledialog/build/xcode4/test_opendialog.xcodeproj/project.pbxproj +294 -0
  53. data/deps/nativefiledialog/build/xcode4/test_opendialogmultiple.xcodeproj/project.pbxproj +294 -0
  54. data/deps/nativefiledialog/build/xcode4/test_pickfolder.xcodeproj/project.pbxproj +294 -0
  55. data/deps/nativefiledialog/build/xcode4/test_savedialog.xcodeproj/project.pbxproj +294 -0
  56. data/deps/nativefiledialog/docs/build.md +39 -0
  57. data/deps/nativefiledialog/docs/contributing.md +25 -0
  58. data/deps/nativefiledialog/screens/open_cocoa.png +0 -0
  59. data/deps/nativefiledialog/screens/open_gtk3.png +0 -0
  60. data/deps/nativefiledialog/screens/open_win.png +0 -0
  61. data/deps/nativefiledialog/src/common.h +21 -0
  62. data/deps/nativefiledialog/src/include/nfd.h +74 -0
  63. data/deps/nativefiledialog/src/nfd_cocoa.m +286 -0
  64. data/deps/nativefiledialog/src/nfd_common.c +142 -0
  65. data/deps/nativefiledialog/src/nfd_common.h +39 -0
  66. data/deps/nativefiledialog/src/nfd_gtk.c +379 -0
  67. data/deps/nativefiledialog/src/nfd_win.cpp +762 -0
  68. data/deps/nativefiledialog/src/nfd_zenity.c +307 -0
  69. data/deps/nativefiledialog/src/simple_exec.h +218 -0
  70. data/deps/nativefiledialog/test/test_opendialog.c +29 -0
  71. data/deps/nativefiledialog/test/test_opendialogmultiple.c +32 -0
  72. data/deps/nativefiledialog/test/test_pickfolder.c +29 -0
  73. data/deps/nativefiledialog/test/test_savedialog.c +28 -0
  74. data/ext/filedialog/extconf.rb +58 -0
  75. data/ext/filedialog/filedialog.c +118 -0
  76. data/filedialog.gemspec +48 -0
  77. data/lib/filedialog.rb +50 -0
  78. data/lib/filedialog/version.rb +5 -0
  79. metadata +137 -0
@@ -0,0 +1,379 @@
1
+ /*
2
+ Native File Dialog
3
+
4
+ http://www.frogtoss.com/labs
5
+ */
6
+
7
+ #include <stdio.h>
8
+ #include <assert.h>
9
+ #include <string.h>
10
+ #include <gtk/gtk.h>
11
+ #include "nfd.h"
12
+ #include "nfd_common.h"
13
+
14
+
15
+ const char INIT_FAIL_MSG[] = "gtk_init_check failed to initilaize GTK+";
16
+
17
+
18
+ static void AddTypeToFilterName( const char *typebuf, char *filterName, size_t bufsize )
19
+ {
20
+ const char SEP[] = ", ";
21
+
22
+ size_t len = strlen(filterName);
23
+ if ( len != 0 )
24
+ {
25
+ strncat( filterName, SEP, bufsize - len - 1 );
26
+ len += strlen(SEP);
27
+ }
28
+
29
+ strncat( filterName, typebuf, bufsize - len - 1 );
30
+ }
31
+
32
+ static void AddFiltersToDialog( GtkWidget *dialog, const char *filterList )
33
+ {
34
+ GtkFileFilter *filter;
35
+ char typebuf[NFD_MAX_STRLEN] = {0};
36
+ const char *p_filterList = filterList;
37
+ char *p_typebuf = typebuf;
38
+ char filterName[NFD_MAX_STRLEN] = {0};
39
+
40
+ if ( !filterList || strlen(filterList) == 0 )
41
+ return;
42
+
43
+ filter = gtk_file_filter_new();
44
+ while ( 1 )
45
+ {
46
+
47
+ if ( NFDi_IsFilterSegmentChar(*p_filterList) )
48
+ {
49
+ char typebufWildcard[NFD_MAX_STRLEN];
50
+ /* add another type to the filter */
51
+ assert( strlen(typebuf) > 0 );
52
+ assert( strlen(typebuf) < NFD_MAX_STRLEN-1 );
53
+
54
+ snprintf( typebufWildcard, NFD_MAX_STRLEN, "*.%s", typebuf );
55
+ AddTypeToFilterName( typebuf, filterName, NFD_MAX_STRLEN );
56
+
57
+ gtk_file_filter_add_pattern( filter, typebufWildcard );
58
+
59
+ p_typebuf = typebuf;
60
+ memset( typebuf, 0, sizeof(char) * NFD_MAX_STRLEN );
61
+ }
62
+
63
+ if ( *p_filterList == ';' || *p_filterList == '\0' )
64
+ {
65
+ /* end of filter -- add it to the dialog */
66
+
67
+ gtk_file_filter_set_name( filter, filterName );
68
+ gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter );
69
+
70
+ filterName[0] = '\0';
71
+
72
+ if ( *p_filterList == '\0' )
73
+ break;
74
+
75
+ filter = gtk_file_filter_new();
76
+ }
77
+
78
+ if ( !NFDi_IsFilterSegmentChar( *p_filterList ) )
79
+ {
80
+ *p_typebuf = *p_filterList;
81
+ p_typebuf++;
82
+ }
83
+
84
+ p_filterList++;
85
+ }
86
+
87
+ /* always append a wildcard option to the end*/
88
+
89
+ filter = gtk_file_filter_new();
90
+ gtk_file_filter_set_name( filter, "*.*" );
91
+ gtk_file_filter_add_pattern( filter, "*" );
92
+ gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter );
93
+ }
94
+
95
+ static void SetDefaultPath( GtkWidget *dialog, const char *defaultPath )
96
+ {
97
+ if ( !defaultPath || strlen(defaultPath) == 0 )
98
+ return;
99
+
100
+ /* GTK+ manual recommends not specifically setting the default path.
101
+ We do it anyway in order to be consistent across platforms.
102
+
103
+ If consistency with the native OS is preferred, this is the line
104
+ to comment out. -ml */
105
+ gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), defaultPath );
106
+ }
107
+
108
+ static nfdresult_t AllocPathSet( GSList *fileList, nfdpathset_t *pathSet )
109
+ {
110
+ size_t bufSize = 0;
111
+ GSList *node;
112
+ nfdchar_t *p_buf;
113
+ size_t count = 0;
114
+
115
+ assert(fileList);
116
+ assert(pathSet);
117
+
118
+ pathSet->count = (size_t)g_slist_length( fileList );
119
+ assert( pathSet->count > 0 );
120
+
121
+ pathSet->indices = NFDi_Malloc( sizeof(size_t)*pathSet->count );
122
+ if ( !pathSet->indices )
123
+ {
124
+ return NFD_ERROR;
125
+ }
126
+
127
+ /* count the total space needed for buf */
128
+ for ( node = fileList; node; node = node->next )
129
+ {
130
+ assert(node->data);
131
+ bufSize += strlen( (const gchar*)node->data ) + 1;
132
+ }
133
+
134
+ pathSet->buf = NFDi_Malloc( sizeof(nfdchar_t) * bufSize );
135
+
136
+ /* fill buf */
137
+ p_buf = pathSet->buf;
138
+ for ( node = fileList; node; node = node->next )
139
+ {
140
+ nfdchar_t *path = (nfdchar_t*)(node->data);
141
+ size_t byteLen = strlen(path)+1;
142
+ ptrdiff_t index;
143
+
144
+ memcpy( p_buf, path, byteLen );
145
+ g_free(node->data);
146
+
147
+ index = p_buf - pathSet->buf;
148
+ assert( index >= 0 );
149
+ pathSet->indices[count] = (size_t)index;
150
+
151
+ p_buf += byteLen;
152
+ ++count;
153
+ }
154
+
155
+ g_slist_free( fileList );
156
+
157
+ return NFD_OKAY;
158
+ }
159
+
160
+ static void WaitForCleanup(void)
161
+ {
162
+ while (gtk_events_pending())
163
+ gtk_main_iteration();
164
+ }
165
+
166
+ /* public */
167
+
168
+ nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList,
169
+ const nfdchar_t *defaultPath,
170
+ nfdchar_t **outPath )
171
+ {
172
+ GtkWidget *dialog;
173
+ nfdresult_t result;
174
+
175
+ if ( !gtk_init_check( NULL, NULL ) )
176
+ {
177
+ NFDi_SetError(INIT_FAIL_MSG);
178
+ return NFD_ERROR;
179
+ }
180
+
181
+ dialog = gtk_file_chooser_dialog_new( "Open File",
182
+ NULL,
183
+ GTK_FILE_CHOOSER_ACTION_OPEN,
184
+ "_Cancel", GTK_RESPONSE_CANCEL,
185
+ "_Open", GTK_RESPONSE_ACCEPT,
186
+ NULL );
187
+
188
+ /* Build the filter list */
189
+ AddFiltersToDialog(dialog, filterList);
190
+
191
+ /* Set the default path */
192
+ SetDefaultPath(dialog, defaultPath);
193
+
194
+ result = NFD_CANCEL;
195
+ if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
196
+ {
197
+ char *filename;
198
+
199
+ filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
200
+
201
+ {
202
+ size_t len = strlen(filename);
203
+ *outPath = NFDi_Malloc( len + 1 );
204
+ memcpy( *outPath, filename, len + 1 );
205
+ if ( !*outPath )
206
+ {
207
+ g_free( filename );
208
+ gtk_widget_destroy(dialog);
209
+ return NFD_ERROR;
210
+ }
211
+ }
212
+ g_free( filename );
213
+
214
+ result = NFD_OKAY;
215
+ }
216
+
217
+ WaitForCleanup();
218
+ gtk_widget_destroy(dialog);
219
+ WaitForCleanup();
220
+
221
+ return result;
222
+ }
223
+
224
+
225
+ nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
226
+ const nfdchar_t *defaultPath,
227
+ nfdpathset_t *outPaths )
228
+ {
229
+ GtkWidget *dialog;
230
+ nfdresult_t result;
231
+
232
+ if ( !gtk_init_check( NULL, NULL ) )
233
+ {
234
+ NFDi_SetError(INIT_FAIL_MSG);
235
+ return NFD_ERROR;
236
+ }
237
+
238
+ dialog = gtk_file_chooser_dialog_new( "Open Files",
239
+ NULL,
240
+ GTK_FILE_CHOOSER_ACTION_OPEN,
241
+ "_Cancel", GTK_RESPONSE_CANCEL,
242
+ "_Open", GTK_RESPONSE_ACCEPT,
243
+ NULL );
244
+ gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER(dialog), TRUE );
245
+
246
+ /* Build the filter list */
247
+ AddFiltersToDialog(dialog, filterList);
248
+
249
+ /* Set the default path */
250
+ SetDefaultPath(dialog, defaultPath);
251
+
252
+ result = NFD_CANCEL;
253
+ if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
254
+ {
255
+ GSList *fileList = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER(dialog) );
256
+ if ( AllocPathSet( fileList, outPaths ) == NFD_ERROR )
257
+ {
258
+ gtk_widget_destroy(dialog);
259
+ return NFD_ERROR;
260
+ }
261
+
262
+ result = NFD_OKAY;
263
+ }
264
+
265
+ WaitForCleanup();
266
+ gtk_widget_destroy(dialog);
267
+ WaitForCleanup();
268
+
269
+ return result;
270
+ }
271
+
272
+ nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
273
+ const nfdchar_t *defaultPath,
274
+ nfdchar_t **outPath )
275
+ {
276
+ GtkWidget *dialog;
277
+ nfdresult_t result;
278
+
279
+ if ( !gtk_init_check( NULL, NULL ) )
280
+ {
281
+ NFDi_SetError(INIT_FAIL_MSG);
282
+ return NFD_ERROR;
283
+ }
284
+
285
+ dialog = gtk_file_chooser_dialog_new( "Save File",
286
+ NULL,
287
+ GTK_FILE_CHOOSER_ACTION_SAVE,
288
+ "_Cancel", GTK_RESPONSE_CANCEL,
289
+ "_Save", GTK_RESPONSE_ACCEPT,
290
+ NULL );
291
+ gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE );
292
+
293
+ /* Build the filter list */
294
+ AddFiltersToDialog(dialog, filterList);
295
+
296
+ /* Set the default path */
297
+ SetDefaultPath(dialog, defaultPath);
298
+
299
+ result = NFD_CANCEL;
300
+ if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
301
+ {
302
+ char *filename;
303
+ filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
304
+
305
+ {
306
+ size_t len = strlen(filename);
307
+ *outPath = NFDi_Malloc( len + 1 );
308
+ memcpy( *outPath, filename, len + 1 );
309
+ if ( !*outPath )
310
+ {
311
+ g_free( filename );
312
+ gtk_widget_destroy(dialog);
313
+ return NFD_ERROR;
314
+ }
315
+ }
316
+ g_free(filename);
317
+
318
+ result = NFD_OKAY;
319
+ }
320
+
321
+ WaitForCleanup();
322
+ gtk_widget_destroy(dialog);
323
+ WaitForCleanup();
324
+
325
+ return result;
326
+ }
327
+
328
+ nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath,
329
+ nfdchar_t **outPath)
330
+ {
331
+ GtkWidget *dialog;
332
+ nfdresult_t result;
333
+
334
+ if (!gtk_init_check(NULL, NULL))
335
+ {
336
+ NFDi_SetError(INIT_FAIL_MSG);
337
+ return NFD_ERROR;
338
+ }
339
+
340
+ dialog = gtk_file_chooser_dialog_new( "Select folder",
341
+ NULL,
342
+ GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
343
+ "_Cancel", GTK_RESPONSE_CANCEL,
344
+ "_Select", GTK_RESPONSE_ACCEPT,
345
+ NULL );
346
+ gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE );
347
+
348
+
349
+ /* Set the default path */
350
+ SetDefaultPath(dialog, defaultPath);
351
+
352
+ result = NFD_CANCEL;
353
+ if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
354
+ {
355
+ char *filename;
356
+ filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
357
+
358
+ {
359
+ size_t len = strlen(filename);
360
+ *outPath = NFDi_Malloc( len + 1 );
361
+ memcpy( *outPath, filename, len + 1 );
362
+ if ( !*outPath )
363
+ {
364
+ g_free( filename );
365
+ gtk_widget_destroy(dialog);
366
+ return NFD_ERROR;
367
+ }
368
+ }
369
+ g_free(filename);
370
+
371
+ result = NFD_OKAY;
372
+ }
373
+
374
+ WaitForCleanup();
375
+ gtk_widget_destroy(dialog);
376
+ WaitForCleanup();
377
+
378
+ return result;
379
+ }
@@ -0,0 +1,762 @@
1
+ /*
2
+ Native File Dialog
3
+
4
+ http://www.frogtoss.com/labs
5
+ */
6
+
7
+
8
+ #ifdef __MINGW32__
9
+ // Explicitly setting NTDDI version, this is necessary for the MinGW compiler
10
+ #define NTDDI_VERSION NTDDI_VISTA
11
+ #define _WIN32_WINNT _WIN32_WINNT_VISTA
12
+ #endif
13
+
14
+ #define _CRTDBG_MAP_ALLOC
15
+ #include <stdlib.h>
16
+ #include <crtdbg.h>
17
+
18
+ /* only locally define UNICODE in this compilation unit */
19
+ #ifndef UNICODE
20
+ #define UNICODE
21
+ #endif
22
+
23
+ #include <wchar.h>
24
+ #include <stdio.h>
25
+ #include <assert.h>
26
+ #include <windows.h>
27
+ #include <shobjidl.h>
28
+ #include "nfd_common.h"
29
+
30
+
31
+ #define COM_INITFLAGS ::COINIT_APARTMENTTHREADED | ::COINIT_DISABLE_OLE1DDE
32
+
33
+ static BOOL COMIsInitialized(HRESULT coResult)
34
+ {
35
+ if (coResult == RPC_E_CHANGED_MODE)
36
+ {
37
+ // If COM was previously initialized with different init flags,
38
+ // NFD still needs to operate. Eat this warning.
39
+ return TRUE;
40
+ }
41
+
42
+ return SUCCEEDED(coResult);
43
+ }
44
+
45
+ static HRESULT COMInit(void)
46
+ {
47
+ return ::CoInitializeEx(NULL, COM_INITFLAGS);
48
+ }
49
+
50
+ static void COMUninit(HRESULT coResult)
51
+ {
52
+ // do not uninitialize if RPC_E_CHANGED_MODE occurred -- this
53
+ // case does not refcount COM.
54
+ if (SUCCEEDED(coResult))
55
+ ::CoUninitialize();
56
+ }
57
+
58
+ // allocs the space in outPath -- call free()
59
+ static void CopyWCharToNFDChar( const wchar_t *inStr, nfdchar_t **outStr )
60
+ {
61
+ int inStrCharacterCount = static_cast<int>(wcslen(inStr));
62
+ int bytesNeeded = WideCharToMultiByte( CP_UTF8, 0,
63
+ inStr, inStrCharacterCount,
64
+ NULL, 0, NULL, NULL );
65
+ assert( bytesNeeded );
66
+ bytesNeeded += 1;
67
+
68
+ *outStr = (nfdchar_t*)NFDi_Malloc( bytesNeeded );
69
+ if ( !*outStr )
70
+ return;
71
+
72
+ int bytesWritten = WideCharToMultiByte( CP_UTF8, 0,
73
+ inStr, -1,
74
+ *outStr, bytesNeeded,
75
+ NULL, NULL );
76
+ assert( bytesWritten ); _NFD_UNUSED( bytesWritten );
77
+ }
78
+
79
+ /* includes NULL terminator byte in return */
80
+ static size_t GetUTF8ByteCountForWChar( const wchar_t *str )
81
+ {
82
+ size_t bytesNeeded = WideCharToMultiByte( CP_UTF8, 0,
83
+ str, -1,
84
+ NULL, 0, NULL, NULL );
85
+ assert( bytesNeeded );
86
+ return bytesNeeded+1;
87
+ }
88
+
89
+ // write to outPtr -- no free() necessary.
90
+ static int CopyWCharToExistingNFDCharBuffer( const wchar_t *inStr, nfdchar_t *outPtr )
91
+ {
92
+ int bytesNeeded = static_cast<int>(GetUTF8ByteCountForWChar( inStr ));
93
+
94
+ /* invocation copies null term */
95
+ int bytesWritten = WideCharToMultiByte( CP_UTF8, 0,
96
+ inStr, -1,
97
+ outPtr, bytesNeeded,
98
+ NULL, 0 );
99
+ assert( bytesWritten );
100
+
101
+ return bytesWritten;
102
+
103
+ }
104
+
105
+
106
+ // allocs the space in outStr -- call free()
107
+ static void CopyNFDCharToWChar( const nfdchar_t *inStr, wchar_t **outStr )
108
+ {
109
+ int inStrByteCount = static_cast<int>(strlen(inStr));
110
+ int charsNeeded = MultiByteToWideChar(CP_UTF8, 0,
111
+ inStr, inStrByteCount,
112
+ NULL, 0 );
113
+ assert( charsNeeded );
114
+ assert( !*outStr );
115
+ charsNeeded += 1; // terminator
116
+
117
+ *outStr = (wchar_t*)NFDi_Malloc( charsNeeded * sizeof(wchar_t) );
118
+ if ( !*outStr )
119
+ return;
120
+
121
+ int ret = MultiByteToWideChar(CP_UTF8, 0,
122
+ inStr, inStrByteCount,
123
+ *outStr, charsNeeded);
124
+ (*outStr)[charsNeeded-1] = '\0';
125
+
126
+ #ifdef _DEBUG
127
+ int inStrCharacterCount = static_cast<int>(NFDi_UTF8_Strlen(inStr));
128
+ assert( ret == inStrCharacterCount );
129
+ #else
130
+ _NFD_UNUSED(ret);
131
+ #endif
132
+ }
133
+
134
+
135
+ /* ext is in format "jpg", no wildcards or separators */
136
+ static int AppendExtensionToSpecBuf( const char *ext, char *specBuf, size_t specBufLen )
137
+ {
138
+ const char SEP[] = ";";
139
+ assert( specBufLen > strlen(ext)+3 );
140
+
141
+ if ( strlen(specBuf) > 0 )
142
+ {
143
+ strncat( specBuf, SEP, specBufLen - strlen(specBuf) - 1 );
144
+ specBufLen += strlen(SEP);
145
+ }
146
+
147
+ char extWildcard[NFD_MAX_STRLEN];
148
+ int bytesWritten = sprintf_s( extWildcard, NFD_MAX_STRLEN, "*.%s", ext );
149
+ assert( bytesWritten == (int)(strlen(ext)+2) );
150
+ _NFD_UNUSED(bytesWritten);
151
+
152
+ strncat( specBuf, extWildcard, specBufLen - strlen(specBuf) - 1 );
153
+
154
+ return NFD_OKAY;
155
+ }
156
+
157
+ static nfdresult_t AddFiltersToDialog( ::IFileDialog *fileOpenDialog, const char *filterList )
158
+ {
159
+ const wchar_t WILDCARD[] = L"*.*";
160
+
161
+ if ( !filterList || strlen(filterList) == 0 )
162
+ return NFD_OKAY;
163
+
164
+ // Count rows to alloc
165
+ UINT filterCount = 1; /* guaranteed to have one filter on a correct, non-empty parse */
166
+ const char *p_filterList;
167
+ for ( p_filterList = filterList; *p_filterList; ++p_filterList )
168
+ {
169
+ if ( *p_filterList == ';' )
170
+ ++filterCount;
171
+ }
172
+
173
+ assert(filterCount);
174
+ if ( !filterCount )
175
+ {
176
+ NFDi_SetError("Error parsing filters.");
177
+ return NFD_ERROR;
178
+ }
179
+
180
+ /* filterCount plus 1 because we hardcode the *.* wildcard after the while loop */
181
+ COMDLG_FILTERSPEC *specList = (COMDLG_FILTERSPEC*)NFDi_Malloc( sizeof(COMDLG_FILTERSPEC) * ((size_t)filterCount + 1) );
182
+ if ( !specList )
183
+ {
184
+ return NFD_ERROR;
185
+ }
186
+ for (UINT i = 0; i < filterCount+1; ++i )
187
+ {
188
+ specList[i].pszName = NULL;
189
+ specList[i].pszSpec = NULL;
190
+ }
191
+
192
+ size_t specIdx = 0;
193
+ p_filterList = filterList;
194
+ char typebuf[NFD_MAX_STRLEN] = {0}; /* one per comma or semicolon */
195
+ char *p_typebuf = typebuf;
196
+
197
+ char specbuf[NFD_MAX_STRLEN] = {0}; /* one per semicolon */
198
+
199
+ while ( 1 )
200
+ {
201
+ if ( NFDi_IsFilterSegmentChar(*p_filterList) )
202
+ {
203
+ /* append a type to the specbuf (pending filter) */
204
+ AppendExtensionToSpecBuf( typebuf, specbuf, NFD_MAX_STRLEN );
205
+
206
+ p_typebuf = typebuf;
207
+ memset( typebuf, 0, sizeof(char)*NFD_MAX_STRLEN );
208
+ }
209
+
210
+ if ( *p_filterList == ';' || *p_filterList == '\0' )
211
+ {
212
+ /* end of filter -- add it to specList */
213
+
214
+ CopyNFDCharToWChar( specbuf, (wchar_t**)&specList[specIdx].pszName );
215
+ CopyNFDCharToWChar( specbuf, (wchar_t**)&specList[specIdx].pszSpec );
216
+
217
+ memset( specbuf, 0, sizeof(char)*NFD_MAX_STRLEN );
218
+ ++specIdx;
219
+ if ( specIdx == filterCount )
220
+ break;
221
+ }
222
+
223
+ if ( !NFDi_IsFilterSegmentChar( *p_filterList ))
224
+ {
225
+ *p_typebuf = *p_filterList;
226
+ ++p_typebuf;
227
+ }
228
+
229
+ ++p_filterList;
230
+ }
231
+
232
+ /* Add wildcard */
233
+ specList[specIdx].pszSpec = WILDCARD;
234
+ specList[specIdx].pszName = WILDCARD;
235
+
236
+ fileOpenDialog->SetFileTypes( filterCount+1, specList );
237
+
238
+ /* free speclist */
239
+ for ( size_t i = 0; i < filterCount; ++i )
240
+ {
241
+ NFDi_Free( (void*)specList[i].pszSpec );
242
+ }
243
+ NFDi_Free( specList );
244
+
245
+ return NFD_OKAY;
246
+ }
247
+
248
+ static nfdresult_t AllocPathSet( IShellItemArray *shellItems, nfdpathset_t *pathSet )
249
+ {
250
+ const char ERRORMSG[] = "Error allocating pathset.";
251
+
252
+ assert(shellItems);
253
+ assert(pathSet);
254
+
255
+ // How many items in shellItems?
256
+ DWORD numShellItems;
257
+ HRESULT result = shellItems->GetCount(&numShellItems);
258
+ if ( !SUCCEEDED(result) )
259
+ {
260
+ NFDi_SetError(ERRORMSG);
261
+ return NFD_ERROR;
262
+ }
263
+
264
+ pathSet->count = static_cast<size_t>(numShellItems);
265
+ assert( pathSet->count > 0 );
266
+
267
+ pathSet->indices = (size_t*)NFDi_Malloc( sizeof(size_t)*pathSet->count );
268
+ if ( !pathSet->indices )
269
+ {
270
+ return NFD_ERROR;
271
+ }
272
+
273
+ /* count the total bytes needed for buf */
274
+ size_t bufSize = 0;
275
+ for ( DWORD i = 0; i < numShellItems; ++i )
276
+ {
277
+ ::IShellItem *shellItem;
278
+ result = shellItems->GetItemAt(i, &shellItem);
279
+ if ( !SUCCEEDED(result) )
280
+ {
281
+ NFDi_SetError(ERRORMSG);
282
+ return NFD_ERROR;
283
+ }
284
+
285
+ // Confirm SFGAO_FILESYSTEM is true for this shellitem, or ignore it.
286
+ SFGAOF attribs;
287
+ result = shellItem->GetAttributes( SFGAO_FILESYSTEM, &attribs );
288
+ if ( !SUCCEEDED(result) )
289
+ {
290
+ NFDi_SetError(ERRORMSG);
291
+ return NFD_ERROR;
292
+ }
293
+ if ( !(attribs & SFGAO_FILESYSTEM) )
294
+ continue;
295
+
296
+ LPWSTR name;
297
+ shellItem->GetDisplayName(SIGDN_FILESYSPATH, &name);
298
+
299
+ // Calculate length of name with UTF-8 encoding
300
+ bufSize += GetUTF8ByteCountForWChar( name );
301
+
302
+ CoTaskMemFree(name);
303
+ }
304
+
305
+ assert(bufSize);
306
+
307
+ pathSet->buf = (nfdchar_t*)NFDi_Malloc( sizeof(nfdchar_t) * bufSize );
308
+ memset( pathSet->buf, 0, sizeof(nfdchar_t) * bufSize );
309
+
310
+ /* fill buf */
311
+ nfdchar_t *p_buf = pathSet->buf;
312
+ for (DWORD i = 0; i < numShellItems; ++i )
313
+ {
314
+ ::IShellItem *shellItem;
315
+ result = shellItems->GetItemAt(i, &shellItem);
316
+ if ( !SUCCEEDED(result) )
317
+ {
318
+ NFDi_SetError(ERRORMSG);
319
+ return NFD_ERROR;
320
+ }
321
+
322
+ // Confirm SFGAO_FILESYSTEM is true for this shellitem, or ignore it.
323
+ SFGAOF attribs;
324
+ result = shellItem->GetAttributes( SFGAO_FILESYSTEM, &attribs );
325
+ if ( !SUCCEEDED(result) )
326
+ {
327
+ NFDi_SetError(ERRORMSG);
328
+ return NFD_ERROR;
329
+ }
330
+ if ( !(attribs & SFGAO_FILESYSTEM) )
331
+ continue;
332
+
333
+ LPWSTR name;
334
+ shellItem->GetDisplayName(SIGDN_FILESYSPATH, &name);
335
+
336
+ int bytesWritten = CopyWCharToExistingNFDCharBuffer(name, p_buf);
337
+ CoTaskMemFree(name);
338
+
339
+ ptrdiff_t index = p_buf - pathSet->buf;
340
+ assert( index >= 0 );
341
+ pathSet->indices[i] = static_cast<size_t>(index);
342
+
343
+ p_buf += bytesWritten;
344
+ }
345
+
346
+ return NFD_OKAY;
347
+ }
348
+
349
+
350
+ static nfdresult_t SetDefaultPath( IFileDialog *dialog, const char *defaultPath )
351
+ {
352
+ if ( !defaultPath || strlen(defaultPath) == 0 )
353
+ return NFD_OKAY;
354
+
355
+ wchar_t *defaultPathW = {0};
356
+ CopyNFDCharToWChar( defaultPath, &defaultPathW );
357
+
358
+ IShellItem *folder;
359
+ HRESULT result = SHCreateItemFromParsingName( defaultPathW, NULL, IID_PPV_ARGS(&folder) );
360
+
361
+ // Valid non results.
362
+ if ( result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || result == HRESULT_FROM_WIN32(ERROR_INVALID_DRIVE) )
363
+ {
364
+ NFDi_Free( defaultPathW );
365
+ return NFD_OKAY;
366
+ }
367
+
368
+ if ( !SUCCEEDED(result) )
369
+ {
370
+ NFDi_SetError("Error creating ShellItem");
371
+ NFDi_Free( defaultPathW );
372
+ return NFD_ERROR;
373
+ }
374
+
375
+ // Could also call SetDefaultFolder(), but this guarantees defaultPath -- more consistency across API.
376
+ dialog->SetFolder( folder );
377
+
378
+ NFDi_Free( defaultPathW );
379
+ folder->Release();
380
+
381
+ return NFD_OKAY;
382
+ }
383
+
384
+ /* public */
385
+
386
+
387
+ nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList,
388
+ const nfdchar_t *defaultPath,
389
+ nfdchar_t **outPath )
390
+ {
391
+ nfdresult_t nfdResult = NFD_ERROR;
392
+
393
+
394
+ HRESULT coResult = COMInit();
395
+ if (!COMIsInitialized(coResult))
396
+ {
397
+ NFDi_SetError("Could not initialize COM.");
398
+ return nfdResult;
399
+ }
400
+
401
+ // Create dialog
402
+ ::IFileOpenDialog *fileOpenDialog(NULL);
403
+ HRESULT result = ::CoCreateInstance(::CLSID_FileOpenDialog, NULL,
404
+ CLSCTX_ALL, ::IID_IFileOpenDialog,
405
+ reinterpret_cast<void**>(&fileOpenDialog) );
406
+
407
+ if ( !SUCCEEDED(result) )
408
+ {
409
+ NFDi_SetError("Could not create dialog.");
410
+ goto end;
411
+ }
412
+
413
+ // Build the filter list
414
+ if ( !AddFiltersToDialog( fileOpenDialog, filterList ) )
415
+ {
416
+ goto end;
417
+ }
418
+
419
+ // Set the default path
420
+ if ( !SetDefaultPath( fileOpenDialog, defaultPath ) )
421
+ {
422
+ goto end;
423
+ }
424
+
425
+ // Show the dialog.
426
+ result = fileOpenDialog->Show(NULL);
427
+ if ( SUCCEEDED(result) )
428
+ {
429
+ // Get the file name
430
+ ::IShellItem *shellItem(NULL);
431
+ result = fileOpenDialog->GetResult(&shellItem);
432
+ if ( !SUCCEEDED(result) )
433
+ {
434
+ NFDi_SetError("Could not get shell item from dialog.");
435
+ goto end;
436
+ }
437
+ wchar_t *filePath(NULL);
438
+ result = shellItem->GetDisplayName(::SIGDN_FILESYSPATH, &filePath);
439
+ if ( !SUCCEEDED(result) )
440
+ {
441
+ NFDi_SetError("Could not get file path for selected.");
442
+ shellItem->Release();
443
+ goto end;
444
+ }
445
+
446
+ CopyWCharToNFDChar( filePath, outPath );
447
+ CoTaskMemFree(filePath);
448
+ if ( !*outPath )
449
+ {
450
+ /* error is malloc-based, error message would be redundant */
451
+ shellItem->Release();
452
+ goto end;
453
+ }
454
+
455
+ nfdResult = NFD_OKAY;
456
+ shellItem->Release();
457
+ }
458
+ else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
459
+ {
460
+ nfdResult = NFD_CANCEL;
461
+ }
462
+ else
463
+ {
464
+ NFDi_SetError("File dialog box show failed.");
465
+ nfdResult = NFD_ERROR;
466
+ }
467
+
468
+ end:
469
+ if (fileOpenDialog)
470
+ fileOpenDialog->Release();
471
+
472
+ COMUninit(coResult);
473
+
474
+ return nfdResult;
475
+ }
476
+
477
+ nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
478
+ const nfdchar_t *defaultPath,
479
+ nfdpathset_t *outPaths )
480
+ {
481
+ nfdresult_t nfdResult = NFD_ERROR;
482
+
483
+
484
+ HRESULT coResult = COMInit();
485
+ if (!COMIsInitialized(coResult))
486
+ {
487
+ NFDi_SetError("Could not initialize COM.");
488
+ return nfdResult;
489
+ }
490
+
491
+ // Create dialog
492
+ ::IFileOpenDialog *fileOpenDialog(NULL);
493
+ HRESULT result = ::CoCreateInstance(::CLSID_FileOpenDialog, NULL,
494
+ CLSCTX_ALL, ::IID_IFileOpenDialog,
495
+ reinterpret_cast<void**>(&fileOpenDialog) );
496
+
497
+ if ( !SUCCEEDED(result) )
498
+ {
499
+ fileOpenDialog = NULL;
500
+ NFDi_SetError("Could not create dialog.");
501
+ goto end;
502
+ }
503
+
504
+ // Build the filter list
505
+ if ( !AddFiltersToDialog( fileOpenDialog, filterList ) )
506
+ {
507
+ goto end;
508
+ }
509
+
510
+ // Set the default path
511
+ if ( !SetDefaultPath( fileOpenDialog, defaultPath ) )
512
+ {
513
+ goto end;
514
+ }
515
+
516
+ // Set a flag for multiple options
517
+ DWORD dwFlags;
518
+ result = fileOpenDialog->GetOptions(&dwFlags);
519
+ if ( !SUCCEEDED(result) )
520
+ {
521
+ NFDi_SetError("Could not get options.");
522
+ goto end;
523
+ }
524
+ result = fileOpenDialog->SetOptions(dwFlags | FOS_ALLOWMULTISELECT);
525
+ if ( !SUCCEEDED(result) )
526
+ {
527
+ NFDi_SetError("Could not set options.");
528
+ goto end;
529
+ }
530
+
531
+ // Show the dialog.
532
+ result = fileOpenDialog->Show(NULL);
533
+ if ( SUCCEEDED(result) )
534
+ {
535
+ IShellItemArray *shellItems;
536
+ result = fileOpenDialog->GetResults( &shellItems );
537
+ if ( !SUCCEEDED(result) )
538
+ {
539
+ NFDi_SetError("Could not get shell items.");
540
+ goto end;
541
+ }
542
+
543
+ if ( AllocPathSet( shellItems, outPaths ) == NFD_ERROR )
544
+ {
545
+ shellItems->Release();
546
+ goto end;
547
+ }
548
+
549
+ shellItems->Release();
550
+ nfdResult = NFD_OKAY;
551
+ }
552
+ else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
553
+ {
554
+ nfdResult = NFD_CANCEL;
555
+ }
556
+ else
557
+ {
558
+ NFDi_SetError("File dialog box show failed.");
559
+ nfdResult = NFD_ERROR;
560
+ }
561
+
562
+ end:
563
+ if ( fileOpenDialog )
564
+ fileOpenDialog->Release();
565
+
566
+ COMUninit(coResult);
567
+
568
+ return nfdResult;
569
+ }
570
+
571
+ nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
572
+ const nfdchar_t *defaultPath,
573
+ nfdchar_t **outPath )
574
+ {
575
+ nfdresult_t nfdResult = NFD_ERROR;
576
+
577
+ HRESULT coResult = COMInit();
578
+ if (!COMIsInitialized(coResult))
579
+ {
580
+ NFDi_SetError("Could not initialize COM.");
581
+ return nfdResult;
582
+ }
583
+
584
+ // Create dialog
585
+ ::IFileSaveDialog *fileSaveDialog(NULL);
586
+ HRESULT result = ::CoCreateInstance(::CLSID_FileSaveDialog, NULL,
587
+ CLSCTX_ALL, ::IID_IFileSaveDialog,
588
+ reinterpret_cast<void**>(&fileSaveDialog) );
589
+
590
+ if ( !SUCCEEDED(result) )
591
+ {
592
+ fileSaveDialog = NULL;
593
+ NFDi_SetError("Could not create dialog.");
594
+ goto end;
595
+ }
596
+
597
+ // Build the filter list
598
+ if ( !AddFiltersToDialog( fileSaveDialog, filterList ) )
599
+ {
600
+ goto end;
601
+ }
602
+
603
+ // Set the default path
604
+ if ( !SetDefaultPath( fileSaveDialog, defaultPath ) )
605
+ {
606
+ goto end;
607
+ }
608
+
609
+ // Show the dialog.
610
+ result = fileSaveDialog->Show(NULL);
611
+ if ( SUCCEEDED(result) )
612
+ {
613
+ // Get the file name
614
+ ::IShellItem *shellItem;
615
+ result = fileSaveDialog->GetResult(&shellItem);
616
+ if ( !SUCCEEDED(result) )
617
+ {
618
+ NFDi_SetError("Could not get shell item from dialog.");
619
+ goto end;
620
+ }
621
+ wchar_t *filePath(NULL);
622
+ result = shellItem->GetDisplayName(::SIGDN_FILESYSPATH, &filePath);
623
+ if ( !SUCCEEDED(result) )
624
+ {
625
+ shellItem->Release();
626
+ NFDi_SetError("Could not get file path for selected.");
627
+ goto end;
628
+ }
629
+
630
+ CopyWCharToNFDChar( filePath, outPath );
631
+ CoTaskMemFree(filePath);
632
+ if ( !*outPath )
633
+ {
634
+ /* error is malloc-based, error message would be redundant */
635
+ shellItem->Release();
636
+ goto end;
637
+ }
638
+
639
+ nfdResult = NFD_OKAY;
640
+ shellItem->Release();
641
+ }
642
+ else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
643
+ {
644
+ nfdResult = NFD_CANCEL;
645
+ }
646
+ else
647
+ {
648
+ NFDi_SetError("File dialog box show failed.");
649
+ nfdResult = NFD_ERROR;
650
+ }
651
+
652
+ end:
653
+ if ( fileSaveDialog )
654
+ fileSaveDialog->Release();
655
+
656
+ COMUninit(coResult);
657
+
658
+ return nfdResult;
659
+ }
660
+
661
+
662
+
663
+ nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath,
664
+ nfdchar_t **outPath)
665
+ {
666
+ nfdresult_t nfdResult = NFD_ERROR;
667
+ DWORD dwOptions = 0;
668
+
669
+ HRESULT coResult = COMInit();
670
+ if (!COMIsInitialized(coResult))
671
+ {
672
+ NFDi_SetError("CoInitializeEx failed.");
673
+ return nfdResult;
674
+ }
675
+
676
+ // Create dialog
677
+ ::IFileOpenDialog *fileDialog(NULL);
678
+ HRESULT result = CoCreateInstance(CLSID_FileOpenDialog,
679
+ NULL,
680
+ CLSCTX_ALL,
681
+ IID_PPV_ARGS(&fileDialog));
682
+ if ( !SUCCEEDED(result) )
683
+ {
684
+ NFDi_SetError("CoCreateInstance for CLSID_FileOpenDialog failed.");
685
+ goto end;
686
+ }
687
+
688
+ // Set the default path
689
+ if (SetDefaultPath(fileDialog, defaultPath) != NFD_OKAY)
690
+ {
691
+ NFDi_SetError("SetDefaultPath failed.");
692
+ goto end;
693
+ }
694
+
695
+ // Get the dialogs options
696
+ if (!SUCCEEDED(fileDialog->GetOptions(&dwOptions)))
697
+ {
698
+ NFDi_SetError("GetOptions for IFileDialog failed.");
699
+ goto end;
700
+ }
701
+
702
+ // Add in FOS_PICKFOLDERS which hides files and only allows selection of folders
703
+ if (!SUCCEEDED(fileDialog->SetOptions(dwOptions | FOS_PICKFOLDERS)))
704
+ {
705
+ NFDi_SetError("SetOptions for IFileDialog failed.");
706
+ goto end;
707
+ }
708
+
709
+ // Show the dialog to the user
710
+ result = fileDialog->Show(NULL);
711
+ if ( SUCCEEDED(result) )
712
+ {
713
+ // Get the folder name
714
+ ::IShellItem *shellItem(NULL);
715
+
716
+ result = fileDialog->GetResult(&shellItem);
717
+ if ( !SUCCEEDED(result) )
718
+ {
719
+ NFDi_SetError("Could not get file path for selected.");
720
+ shellItem->Release();
721
+ goto end;
722
+ }
723
+
724
+ wchar_t *path = NULL;
725
+ result = shellItem->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &path);
726
+ if ( !SUCCEEDED(result) )
727
+ {
728
+ NFDi_SetError("GetDisplayName for IShellItem failed.");
729
+ shellItem->Release();
730
+ goto end;
731
+ }
732
+
733
+ CopyWCharToNFDChar(path, outPath);
734
+ CoTaskMemFree(path);
735
+ if ( !*outPath )
736
+ {
737
+ shellItem->Release();
738
+ goto end;
739
+ }
740
+
741
+ nfdResult = NFD_OKAY;
742
+ shellItem->Release();
743
+ }
744
+ else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
745
+ {
746
+ nfdResult = NFD_CANCEL;
747
+ }
748
+ else
749
+ {
750
+ NFDi_SetError("Show for IFileDialog failed.");
751
+ nfdResult = NFD_ERROR;
752
+ }
753
+
754
+ end:
755
+
756
+ if (fileDialog)
757
+ fileDialog->Release();
758
+
759
+ COMUninit(coResult);
760
+
761
+ return nfdResult;
762
+ }