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