filedialog 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
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
+ }