Skip to content

Commit 41d4f4f

Browse files
author
Semphris
committed
Implement IFileDialog for Windows
1 parent 20f7835 commit 41d4f4f

File tree

1 file changed

+315
-0
lines changed

1 file changed

+315
-0
lines changed

src/dialog/windows/SDL_windowsdialog.c

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <windows.h>
2626
#include <commdlg.h>
2727
#include <shlobj.h>
28+
#include <shobjidl.h>
2829
#include "../../core/windows/SDL_windows.h"
2930
#include "../../thread/SDL_systhread.h"
3031

@@ -35,9 +36,11 @@ typedef struct
3536
{
3637
bool is_save;
3738
wchar_t *filters_str;
39+
int nfilters;
3840
char *default_file;
3941
SDL_Window *parent;
4042
DWORD flags;
43+
bool allow_many;
4144
SDL_DialogFileCallback callback;
4245
void *userdata;
4346
char *title;
@@ -48,6 +51,7 @@ typedef struct
4851
typedef struct
4952
{
5053
SDL_Window *parent;
54+
bool allow_many;
5155
SDL_DialogFileCallback callback;
5256
char *default_folder;
5357
void *userdata;
@@ -101,18 +105,318 @@ char *clear_filt_names(const char *filt)
101105
return cleared;
102106
}
103107

108+
bool windows_ShowModernFileFolderDialog(SDL_FileDialogType dialog_type, const char *default_file, SDL_Window *parent, bool allow_many, SDL_DialogFileCallback callback, void *userdata, const char *title, const char *accept, const char *cancel, wchar_t *filter_wchar, int nfilters)
109+
{
110+
bool is_save = dialog_type == SDL_FILEDIALOG_SAVEFILE;
111+
bool is_folder = dialog_type == SDL_FILEDIALOG_OPENFOLDER;
112+
113+
if (is_save) {
114+
// Just in case; the code relies on that
115+
allow_many = false;
116+
}
117+
118+
IFileDialog *pFileDialog = NULL;
119+
IFileOpenDialog *pFileOpenDialog = NULL;
120+
IFileDialog2 *pFileDialog2 = NULL;
121+
IShellItemArray *pItemArray = NULL;
122+
IShellItem *pItem = NULL;
123+
IShellItem *pFolderItem = NULL;
124+
LPWSTR filePath = NULL;
125+
COMDLG_FILTERSPEC *filter_data = NULL;
126+
char **files = NULL;
127+
wchar_t *title_w = NULL;
128+
wchar_t *accept_w = NULL;
129+
wchar_t *cancel_w = NULL;
130+
FILEOPENDIALOGOPTIONS pfos;
131+
132+
if (filter_wchar && nfilters > 0) {
133+
wchar_t *filter_ptr = filter_wchar;
134+
filter_data = SDL_calloc(sizeof(COMDLG_FILTERSPEC), nfilters);
135+
if (!filter_data) {
136+
goto quit;
137+
}
138+
139+
for (int i = 0; i < nfilters; i++) {
140+
filter_data[i].pszName = filter_ptr;
141+
filter_ptr += SDL_wcslen(filter_ptr) + 1;
142+
filter_data[i].pszSpec = filter_ptr;
143+
filter_ptr += SDL_wcslen(filter_ptr) + 1;
144+
}
145+
146+
// assert(*filter_ptr == L'\0');
147+
}
148+
149+
if (title && !(title_w = WIN_UTF8ToStringW(title))) {
150+
goto quit;
151+
}
152+
153+
if (accept && !(accept_w = WIN_UTF8ToStringW(accept))) {
154+
goto quit;
155+
}
156+
157+
if (cancel && !(cancel_w = WIN_UTF8ToStringW(cancel))) {
158+
goto quit;
159+
}
160+
161+
wchar_t *default_file_w = NULL;
162+
wchar_t *default_folder_w = NULL;
163+
164+
if (default_file) {
165+
default_folder_w = WIN_UTF8ToStringW(default_file);
166+
167+
if (!default_folder_w) {
168+
goto quit;
169+
}
170+
171+
for (wchar_t *chrptr = default_folder_w; *chrptr; chrptr++) {
172+
if (*chrptr == L'/' || *chrptr == L'\\') {
173+
default_file_w = chrptr;
174+
}
175+
}
176+
177+
if (!default_file_w) {
178+
default_file_w = default_folder_w;
179+
default_folder_w = NULL;
180+
} else {
181+
*default_file_w = L'\0';
182+
default_file_w++;
183+
184+
if (SDL_wcslen(default_file_w) == 0) {
185+
default_file_w = NULL;
186+
}
187+
}
188+
}
189+
190+
bool success = false;
191+
bool co_init = false;
192+
193+
#define CHECK(op) if (!SUCCEEDED(op)) { goto quit; }
194+
195+
CHECK(WIN_CoInitialize());
196+
197+
co_init = true;
198+
199+
CHECK(CoCreateInstance(is_save ? &CLSID_FileSaveDialog : &CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, &IID_IFileDialog, (void**)&pFileDialog));
200+
CHECK(pFileDialog->lpVtbl->QueryInterface(pFileDialog, &IID_IFileDialog2, (void**)&pFileDialog2));
201+
202+
if (allow_many) {
203+
CHECK(pFileDialog->lpVtbl->QueryInterface(pFileDialog, &IID_IFileOpenDialog, (void**)&pFileOpenDialog));
204+
}
205+
206+
CHECK(pFileDialog2->lpVtbl->GetOptions(pFileDialog2, &pfos));
207+
208+
pfos |= FOS_NOCHANGEDIR;
209+
if (allow_many) pfos |= FOS_ALLOWMULTISELECT;
210+
if (is_save) pfos |= FOS_OVERWRITEPROMPT;
211+
if (is_folder) pfos |= FOS_PICKFOLDERS;
212+
213+
CHECK(pFileDialog2->lpVtbl->SetOptions(pFileDialog2, pfos));
214+
215+
if (cancel_w) {
216+
CHECK(pFileDialog2->lpVtbl->SetCancelButtonLabel(pFileDialog2, cancel_w));
217+
}
218+
219+
if (accept_w) {
220+
CHECK(pFileDialog->lpVtbl->SetOkButtonLabel(pFileDialog, accept_w));
221+
}
222+
223+
if (title_w) {
224+
CHECK(pFileDialog->lpVtbl->SetTitle(pFileDialog, title_w));
225+
}
226+
227+
if (filter_data) {
228+
CHECK(pFileDialog->lpVtbl->SetFileTypes(pFileDialog, nfilters, filter_data));
229+
}
230+
231+
// SetFolder would enforce using the same location each and every time, but
232+
// Windows docs recommend against it
233+
if (default_folder_w) {
234+
CHECK(SHCreateItemFromParsingName(default_folder_w, NULL, &IID_IShellItem, (void**)&pFolderItem));
235+
CHECK(pFileDialog->lpVtbl->SetDefaultFolder(pFileDialog, pFolderItem));
236+
}
237+
238+
if (default_file_w) {
239+
CHECK(pFileDialog->lpVtbl->SetFileName(pFileDialog, default_file_w));
240+
}
241+
242+
if (parent) {
243+
HWND window = (HWND) SDL_GetPointerProperty(SDL_GetWindowProperties(parent), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
244+
245+
HRESULT hr = pFileDialog->lpVtbl->Show(pFileDialog, window);
246+
247+
if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) {
248+
const char * const results[] = { NULL };
249+
UINT selected_filter;
250+
251+
// This is a one-based index, not zero-based. Doc link in similar comment below
252+
CHECK(pFileDialog->lpVtbl->GetFileTypeIndex(pFileDialog, &selected_filter));
253+
callback(userdata, results, selected_filter - 1);
254+
success = true;
255+
goto quit;
256+
} else if (!SUCCEEDED(hr)) {
257+
goto quit;
258+
}
259+
} else {
260+
HRESULT hr = pFileDialog->lpVtbl->Show(pFileDialog, NULL);
261+
262+
if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) {
263+
const char * const results[] = { NULL };
264+
UINT selected_filter;
265+
266+
// This is a one-based index, not zero-based. Doc link in similar comment below
267+
CHECK(pFileDialog->lpVtbl->GetFileTypeIndex(pFileDialog, &selected_filter));
268+
callback(userdata, results, selected_filter - 1);
269+
success = true;
270+
goto quit;
271+
} else if (!SUCCEEDED(hr)) {
272+
goto quit;
273+
}
274+
}
275+
276+
if (allow_many) {
277+
DWORD nResults;
278+
UINT selected_filter;
279+
280+
CHECK(pFileOpenDialog->lpVtbl->GetResults(pFileOpenDialog, &pItemArray));
281+
CHECK(pFileDialog->lpVtbl->GetFileTypeIndex(pFileDialog, &selected_filter));
282+
CHECK(pItemArray->lpVtbl->GetCount(pItemArray, &nResults));
283+
284+
files = SDL_calloc(nResults + 1, sizeof(char*));
285+
if (!files) {
286+
goto quit;
287+
}
288+
char** files_ptr = files;
289+
290+
for (int i = 0; i < nResults; i++) {
291+
CHECK(pItemArray->lpVtbl->GetItemAt(pItemArray, i, &pItem));
292+
CHECK(pItem->lpVtbl->GetDisplayName(pItem, SIGDN_FILESYSPATH, &filePath));
293+
294+
*(files_ptr++) = WIN_StringToUTF8(filePath);
295+
296+
CoTaskMemFree(filePath);
297+
filePath = NULL;
298+
pItem->lpVtbl->Release(pItem);
299+
pItem = NULL;
300+
}
301+
302+
callback(userdata, (const char * const *) files, selected_filter - 1);
303+
success = true;
304+
} else {
305+
// This is a one-based index, not zero-based.
306+
// https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialog-getfiletypeindex#parameters
307+
UINT selected_filter;
308+
309+
CHECK(pFileDialog->lpVtbl->GetResult(pFileDialog, &pItem));
310+
CHECK(pFileDialog->lpVtbl->GetFileTypeIndex(pFileDialog, &selected_filter));
311+
CHECK(pItem->lpVtbl->GetDisplayName(pItem, SIGDN_FILESYSPATH, &filePath));
312+
313+
char *file = WIN_StringToUTF8(filePath);
314+
if (!file) {
315+
goto quit;
316+
}
317+
const char * const results[] = { file, NULL };
318+
callback(userdata, results, selected_filter - 1);
319+
success = true;
320+
SDL_free(file);
321+
}
322+
323+
success = true;
324+
325+
#undef CHECK
326+
327+
quit:
328+
if (!success) {
329+
WIN_SetError("dialogg");
330+
callback(userdata, NULL, -1);
331+
}
332+
333+
if (co_init) {
334+
WIN_CoUninitialize();
335+
}
336+
337+
if (pFileOpenDialog) {
338+
pFileOpenDialog->lpVtbl->Release(pFileOpenDialog);
339+
}
340+
341+
if (pFileDialog2) {
342+
pFileDialog2->lpVtbl->Release(pFileDialog2);
343+
}
344+
345+
if (pFileDialog) {
346+
pFileDialog->lpVtbl->Release(pFileDialog);
347+
}
348+
349+
if (pItem) {
350+
pItem->lpVtbl->Release(pItem);
351+
}
352+
353+
if (pFolderItem) {
354+
pFolderItem->lpVtbl->Release(pFolderItem);
355+
}
356+
357+
if (pItemArray) {
358+
pItemArray->lpVtbl->Release(pItemArray);
359+
}
360+
361+
if (filePath) {
362+
CoTaskMemFree(filePath);
363+
}
364+
365+
// If both default_file_w and default_folder_w are non-NULL, then
366+
// default_file_w is a pointer into default_folder_w.
367+
if (default_folder_w) {
368+
SDL_free(default_folder_w);
369+
} else if (default_file_w) {
370+
SDL_free(default_file_w);
371+
}
372+
373+
if (title_w) {
374+
SDL_free(title_w);
375+
}
376+
377+
if (accept_w) {
378+
SDL_free(accept_w);
379+
}
380+
381+
if (cancel_w) {
382+
SDL_free(cancel_w);
383+
}
384+
385+
if (filter_data) {
386+
SDL_free(filter_data);
387+
}
388+
389+
if (files) {
390+
for (char** files_ptr = files; *files_ptr; files_ptr++) {
391+
SDL_free(*files_ptr);
392+
}
393+
SDL_free(files);
394+
}
395+
396+
return success;
397+
}
398+
104399
// TODO: The new version of file dialogs
105400
void windows_ShowFileDialog(void *ptr)
106401
{
402+
107403
winArgs *args = (winArgs *) ptr;
108404
bool is_save = args->is_save;
109405
const char *default_file = args->default_file;
110406
SDL_Window *parent = args->parent;
111407
DWORD flags = args->flags;
408+
bool allow_many = args->allow_many;
112409
SDL_DialogFileCallback callback = args->callback;
113410
void *userdata = args->userdata;
114411
const char *title = args->title;
412+
const char *accept = args->accept;
413+
const char *cancel = args->cancel;
115414
wchar_t *filter_wchar = args->filters_str;
415+
int nfilters = args->nfilters;
416+
417+
if (windows_ShowModernFileFolderDialog(is_save ? SDL_FILEDIALOG_SAVEFILE : SDL_FILEDIALOG_OPENFILE, default_file, parent, allow_many, callback, userdata, title, accept, cancel, filter_wchar, nfilters)) {
418+
return;
419+
}
116420

117421
/* GetOpenFileName and GetSaveFileName have the same signature
118422
(yes, LPOPENFILENAMEW even for the save dialog) */
@@ -407,7 +711,15 @@ void windows_ShowFolderDialog(void *ptr)
407711
SDL_DialogFileCallback callback = args->callback;
408712
void *userdata = args->userdata;
409713
HWND parent = NULL;
714+
int allow_many = args->allow_many;
715+
char *default_folder = args->default_folder;
410716
const char *title = args->title;
717+
const char *accept = args->accept;
718+
const char *cancel = args->cancel;
719+
720+
if (windows_ShowModernFileFolderDialog(SDL_FILEDIALOG_OPENFOLDER, default_folder, window, allow_many, callback, userdata, title, accept, cancel, NULL, 0)) {
721+
return;
722+
}
411723

412724
if (window) {
413725
parent = (HWND) SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
@@ -532,9 +844,11 @@ static void ShowFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_
532844

533845
args->is_save = is_save;
534846
args->filters_str = filters_str;
847+
args->nfilters = nfilters;
535848
args->default_file = default_location ? SDL_strdup(default_location) : NULL;
536849
args->parent = window;
537850
args->flags = flags;
851+
args->allow_many = allow_many;
538852
args->callback = callback;
539853
args->userdata = userdata;
540854
args->title = title ? SDL_strdup(title) : NULL;
@@ -571,6 +885,7 @@ void ShowFolderDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Windo
571885
}
572886

573887
args->parent = window;
888+
args->allow_many = allow_many;
574889
args->callback = callback;
575890
args->default_folder = default_location ? SDL_strdup(default_location) : NULL;
576891
args->userdata = userdata;

0 commit comments

Comments
 (0)