diff options
Diffstat (limited to 'contrib/SDL-3.2.8/src/dialog/windows')
| -rw-r--r-- | contrib/SDL-3.2.8/src/dialog/windows/SDL_windowsdialog.c | 611 |
1 files changed, 611 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/dialog/windows/SDL_windowsdialog.c b/contrib/SDL-3.2.8/src/dialog/windows/SDL_windowsdialog.c new file mode 100644 index 0000000..2de224f --- /dev/null +++ b/contrib/SDL-3.2.8/src/dialog/windows/SDL_windowsdialog.c | |||
| @@ -0,0 +1,611 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | #include "../SDL_dialog.h" | ||
| 23 | #include "../SDL_dialog_utils.h" | ||
| 24 | |||
| 25 | #include <windows.h> | ||
| 26 | #include <commdlg.h> | ||
| 27 | #include <shlobj.h> | ||
| 28 | #include "../../core/windows/SDL_windows.h" | ||
| 29 | #include "../../thread/SDL_systhread.h" | ||
| 30 | |||
| 31 | // If this number is too small, selecting too many files will give an error | ||
| 32 | #define SELECTLIST_SIZE 65536 | ||
| 33 | |||
| 34 | typedef struct | ||
| 35 | { | ||
| 36 | bool is_save; | ||
| 37 | wchar_t *filters_str; | ||
| 38 | char *default_file; | ||
| 39 | SDL_Window *parent; | ||
| 40 | DWORD flags; | ||
| 41 | SDL_DialogFileCallback callback; | ||
| 42 | void *userdata; | ||
| 43 | char *title; | ||
| 44 | char *accept; | ||
| 45 | char *cancel; | ||
| 46 | } winArgs; | ||
| 47 | |||
| 48 | typedef struct | ||
| 49 | { | ||
| 50 | SDL_Window *parent; | ||
| 51 | SDL_DialogFileCallback callback; | ||
| 52 | char *default_folder; | ||
| 53 | void *userdata; | ||
| 54 | char *title; | ||
| 55 | char *accept; | ||
| 56 | char *cancel; | ||
| 57 | } winFArgs; | ||
| 58 | |||
| 59 | void freeWinArgs(winArgs *args) | ||
| 60 | { | ||
| 61 | SDL_free(args->default_file); | ||
| 62 | SDL_free(args->filters_str); | ||
| 63 | SDL_free(args->title); | ||
| 64 | SDL_free(args->accept); | ||
| 65 | SDL_free(args->cancel); | ||
| 66 | |||
| 67 | SDL_free(args); | ||
| 68 | } | ||
| 69 | |||
| 70 | void freeWinFArgs(winFArgs *args) | ||
| 71 | { | ||
| 72 | SDL_free(args->default_folder); | ||
| 73 | SDL_free(args->title); | ||
| 74 | SDL_free(args->accept); | ||
| 75 | SDL_free(args->cancel); | ||
| 76 | |||
| 77 | SDL_free(args); | ||
| 78 | } | ||
| 79 | |||
| 80 | /** Converts dialog.nFilterIndex to SDL-compatible value */ | ||
| 81 | int getFilterIndex(int as_reported_by_windows) | ||
| 82 | { | ||
| 83 | return as_reported_by_windows - 1; | ||
| 84 | } | ||
| 85 | |||
| 86 | char *clear_filt_names(const char *filt) | ||
| 87 | { | ||
| 88 | char *cleared = SDL_strdup(filt); | ||
| 89 | |||
| 90 | for (char *c = cleared; *c; c++) { | ||
| 91 | /* 0x01 bytes are used as temporary replacement for the various 0x00 | ||
| 92 | bytes required by Win32 (one null byte between each filter, two at | ||
| 93 | the end of the filters). Filter out these bytes from the filter names | ||
| 94 | to avoid early-ending the filters if someone puts two consecutive | ||
| 95 | 0x01 bytes in their filter names. */ | ||
| 96 | if (*c == '\x01') { | ||
| 97 | *c = ' '; | ||
| 98 | } | ||
| 99 | } | ||
| 100 | |||
| 101 | return cleared; | ||
| 102 | } | ||
| 103 | |||
| 104 | // TODO: The new version of file dialogs | ||
| 105 | void windows_ShowFileDialog(void *ptr) | ||
| 106 | { | ||
| 107 | winArgs *args = (winArgs *) ptr; | ||
| 108 | bool is_save = args->is_save; | ||
| 109 | const char *default_file = args->default_file; | ||
| 110 | SDL_Window *parent = args->parent; | ||
| 111 | DWORD flags = args->flags; | ||
| 112 | SDL_DialogFileCallback callback = args->callback; | ||
| 113 | void *userdata = args->userdata; | ||
| 114 | const char *title = args->title; | ||
| 115 | wchar_t *filter_wchar = args->filters_str; | ||
| 116 | |||
| 117 | /* GetOpenFileName and GetSaveFileName have the same signature | ||
| 118 | (yes, LPOPENFILENAMEW even for the save dialog) */ | ||
| 119 | typedef BOOL (WINAPI *pfnGetAnyFileNameW)(LPOPENFILENAMEW); | ||
| 120 | typedef DWORD (WINAPI *pfnCommDlgExtendedError)(void); | ||
| 121 | HMODULE lib = LoadLibraryW(L"Comdlg32.dll"); | ||
| 122 | pfnGetAnyFileNameW pGetAnyFileName = NULL; | ||
| 123 | pfnCommDlgExtendedError pCommDlgExtendedError = NULL; | ||
| 124 | |||
| 125 | if (lib) { | ||
| 126 | pGetAnyFileName = (pfnGetAnyFileNameW) GetProcAddress(lib, is_save ? "GetSaveFileNameW" : "GetOpenFileNameW"); | ||
| 127 | pCommDlgExtendedError = (pfnCommDlgExtendedError) GetProcAddress(lib, "CommDlgExtendedError"); | ||
| 128 | } else { | ||
| 129 | SDL_SetError("Couldn't load Comdlg32.dll"); | ||
| 130 | callback(userdata, NULL, -1); | ||
| 131 | return; | ||
| 132 | } | ||
| 133 | |||
| 134 | if (!pGetAnyFileName) { | ||
| 135 | SDL_SetError("Couldn't load GetOpenFileName/GetSaveFileName from library"); | ||
| 136 | callback(userdata, NULL, -1); | ||
| 137 | return; | ||
| 138 | } | ||
| 139 | |||
| 140 | if (!pCommDlgExtendedError) { | ||
| 141 | SDL_SetError("Couldn't load CommDlgExtendedError from library"); | ||
| 142 | callback(userdata, NULL, -1); | ||
| 143 | return; | ||
| 144 | } | ||
| 145 | |||
| 146 | HWND window = NULL; | ||
| 147 | |||
| 148 | if (parent) { | ||
| 149 | window = (HWND) SDL_GetPointerProperty(SDL_GetWindowProperties(parent), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); | ||
| 150 | } | ||
| 151 | |||
| 152 | wchar_t *filebuffer; // lpstrFile | ||
| 153 | wchar_t initfolder[MAX_PATH] = L""; // lpstrInitialDir | ||
| 154 | |||
| 155 | /* If SELECTLIST_SIZE is too large, putting filebuffer on the stack might | ||
| 156 | cause an overflow */ | ||
| 157 | filebuffer = (wchar_t *) SDL_malloc(SELECTLIST_SIZE * sizeof(wchar_t)); | ||
| 158 | |||
| 159 | // Necessary for the return code below | ||
| 160 | SDL_memset(filebuffer, 0, SELECTLIST_SIZE * sizeof(wchar_t)); | ||
| 161 | |||
| 162 | if (default_file) { | ||
| 163 | /* On Windows 10, 11 and possibly others, lpstrFile can be initialized | ||
| 164 | with a path and the dialog will start at that location, but *only if | ||
| 165 | the path contains a filename*. If it ends with a folder (directory | ||
| 166 | separator), it fails with 0x3002 (12290) FNERR_INVALIDFILENAME. For | ||
| 167 | that specific case, lpstrInitialDir must be used instead, but just | ||
| 168 | for that case, because lpstrInitialDir doesn't support file names. | ||
| 169 | |||
| 170 | On top of that, lpstrInitialDir hides a special algorithm that | ||
| 171 | decides which folder to actually use as starting point, which may or | ||
| 172 | may not be the one provided, or some other unrelated folder. Also, | ||
| 173 | the algorithm changes between platforms. Assuming the documentation | ||
| 174 | is correct, the algorithm is there under 'lpstrInitialDir': | ||
| 175 | |||
| 176 | https://learn.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-openfilenamew | ||
| 177 | |||
| 178 | Finally, lpstrFile does not support forward slashes. lpstrInitialDir | ||
| 179 | does, though. */ | ||
| 180 | |||
| 181 | char last_c = default_file[SDL_strlen(default_file) - 1]; | ||
| 182 | |||
| 183 | if (last_c == '\\' || last_c == '/') { | ||
| 184 | MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, default_file, -1, initfolder, MAX_PATH); | ||
| 185 | } else { | ||
| 186 | MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, default_file, -1, filebuffer, MAX_PATH); | ||
| 187 | |||
| 188 | for (int i = 0; i < SELECTLIST_SIZE; i++) { | ||
| 189 | if (filebuffer[i] == L'/') { | ||
| 190 | filebuffer[i] = L'\\'; | ||
| 191 | } | ||
| 192 | } | ||
| 193 | } | ||
| 194 | } | ||
| 195 | |||
| 196 | wchar_t *title_w = NULL; | ||
| 197 | |||
| 198 | if (title) { | ||
| 199 | title_w = WIN_UTF8ToStringW(title); | ||
| 200 | if (!title_w) { | ||
| 201 | SDL_free(filebuffer); | ||
| 202 | callback(userdata, NULL, -1); | ||
| 203 | return; | ||
| 204 | } | ||
| 205 | } | ||
| 206 | |||
| 207 | OPENFILENAMEW dialog; | ||
| 208 | dialog.lStructSize = sizeof(OPENFILENAME); | ||
| 209 | dialog.hwndOwner = window; | ||
| 210 | dialog.hInstance = 0; | ||
| 211 | dialog.lpstrFilter = filter_wchar; | ||
| 212 | dialog.lpstrCustomFilter = NULL; | ||
| 213 | dialog.nMaxCustFilter = 0; | ||
| 214 | dialog.nFilterIndex = 0; | ||
| 215 | dialog.lpstrFile = filebuffer; | ||
| 216 | dialog.nMaxFile = SELECTLIST_SIZE; | ||
| 217 | dialog.lpstrFileTitle = NULL; | ||
| 218 | dialog.lpstrInitialDir = *initfolder ? initfolder : NULL; | ||
| 219 | dialog.lpstrTitle = title_w; | ||
| 220 | dialog.Flags = flags | OFN_EXPLORER | OFN_HIDEREADONLY | OFN_NOCHANGEDIR; | ||
| 221 | dialog.nFileOffset = 0; | ||
| 222 | dialog.nFileExtension = 0; | ||
| 223 | dialog.lpstrDefExt = NULL; | ||
| 224 | dialog.lCustData = 0; | ||
| 225 | dialog.lpfnHook = NULL; | ||
| 226 | dialog.lpTemplateName = NULL; | ||
| 227 | // Skipped many mac-exclusive and reserved members | ||
| 228 | dialog.FlagsEx = 0; | ||
| 229 | |||
| 230 | BOOL result = pGetAnyFileName(&dialog); | ||
| 231 | |||
| 232 | SDL_free(title_w); | ||
| 233 | |||
| 234 | if (result) { | ||
| 235 | if (!(flags & OFN_ALLOWMULTISELECT)) { | ||
| 236 | // File is a C string stored in dialog.lpstrFile | ||
| 237 | char *chosen_file = WIN_StringToUTF8W(dialog.lpstrFile); | ||
| 238 | const char *opts[2] = { chosen_file, NULL }; | ||
| 239 | callback(userdata, opts, getFilterIndex(dialog.nFilterIndex)); | ||
| 240 | SDL_free(chosen_file); | ||
| 241 | } else { | ||
| 242 | /* File is either a C string if the user chose a single file, else | ||
| 243 | it's a series of strings formatted like: | ||
| 244 | |||
| 245 | "C:\\path\\to\\folder\0filename1.ext\0filename2.ext\0\0" | ||
| 246 | |||
| 247 | The code below will only stop on a double NULL in all cases, so | ||
| 248 | it is important that the rest of the buffer has been zeroed. */ | ||
| 249 | char chosen_folder[MAX_PATH]; | ||
| 250 | char chosen_file[MAX_PATH]; | ||
| 251 | wchar_t *file_ptr = dialog.lpstrFile; | ||
| 252 | size_t nfiles = 0; | ||
| 253 | size_t chosen_folder_size; | ||
| 254 | char **chosen_files_list = (char **) SDL_malloc(sizeof(char *) * (nfiles + 1)); | ||
| 255 | |||
| 256 | if (!chosen_files_list) { | ||
| 257 | callback(userdata, NULL, -1); | ||
| 258 | SDL_free(filebuffer); | ||
| 259 | return; | ||
| 260 | } | ||
| 261 | |||
| 262 | chosen_files_list[nfiles] = NULL; | ||
| 263 | |||
| 264 | if (WideCharToMultiByte(CP_UTF8, 0, file_ptr, -1, chosen_folder, MAX_PATH, NULL, NULL) >= MAX_PATH) { | ||
| 265 | SDL_SetError("Path too long or invalid character in path"); | ||
| 266 | SDL_free(chosen_files_list); | ||
| 267 | callback(userdata, NULL, -1); | ||
| 268 | SDL_free(filebuffer); | ||
| 269 | return; | ||
| 270 | } | ||
| 271 | |||
| 272 | chosen_folder_size = SDL_strlen(chosen_folder); | ||
| 273 | SDL_strlcpy(chosen_file, chosen_folder, MAX_PATH); | ||
| 274 | chosen_file[chosen_folder_size] = '\\'; | ||
| 275 | |||
| 276 | file_ptr += SDL_strlen(chosen_folder) + 1; | ||
| 277 | |||
| 278 | while (*file_ptr) { | ||
| 279 | nfiles++; | ||
| 280 | char **new_cfl = (char **) SDL_realloc(chosen_files_list, sizeof(char*) * (nfiles + 1)); | ||
| 281 | |||
| 282 | if (!new_cfl) { | ||
| 283 | for (size_t i = 0; i < nfiles - 1; i++) { | ||
| 284 | SDL_free(chosen_files_list[i]); | ||
| 285 | } | ||
| 286 | |||
| 287 | SDL_free(chosen_files_list); | ||
| 288 | callback(userdata, NULL, -1); | ||
| 289 | SDL_free(filebuffer); | ||
| 290 | return; | ||
| 291 | } | ||
| 292 | |||
| 293 | chosen_files_list = new_cfl; | ||
| 294 | chosen_files_list[nfiles] = NULL; | ||
| 295 | |||
| 296 | int diff = ((int) chosen_folder_size) + 1; | ||
| 297 | |||
| 298 | if (WideCharToMultiByte(CP_UTF8, 0, file_ptr, -1, chosen_file + diff, MAX_PATH - diff, NULL, NULL) >= MAX_PATH - diff) { | ||
| 299 | SDL_SetError("Path too long or invalid character in path"); | ||
| 300 | |||
| 301 | for (size_t i = 0; i < nfiles - 1; i++) { | ||
| 302 | SDL_free(chosen_files_list[i]); | ||
| 303 | } | ||
| 304 | |||
| 305 | SDL_free(chosen_files_list); | ||
| 306 | callback(userdata, NULL, -1); | ||
| 307 | SDL_free(filebuffer); | ||
| 308 | return; | ||
| 309 | } | ||
| 310 | |||
| 311 | file_ptr += SDL_strlen(chosen_file) + 1 - diff; | ||
| 312 | |||
| 313 | chosen_files_list[nfiles - 1] = SDL_strdup(chosen_file); | ||
| 314 | |||
| 315 | if (!chosen_files_list[nfiles - 1]) { | ||
| 316 | for (size_t i = 0; i < nfiles - 1; i++) { | ||
| 317 | SDL_free(chosen_files_list[i]); | ||
| 318 | } | ||
| 319 | |||
| 320 | SDL_free(chosen_files_list); | ||
| 321 | callback(userdata, NULL, -1); | ||
| 322 | SDL_free(filebuffer); | ||
| 323 | return; | ||
| 324 | } | ||
| 325 | } | ||
| 326 | |||
| 327 | // If the user chose only one file, it's all just one string | ||
| 328 | if (nfiles == 0) { | ||
| 329 | nfiles++; | ||
| 330 | char **new_cfl = (char **) SDL_realloc(chosen_files_list, sizeof(char*) * (nfiles + 1)); | ||
| 331 | |||
| 332 | if (!new_cfl) { | ||
| 333 | SDL_free(chosen_files_list); | ||
| 334 | callback(userdata, NULL, -1); | ||
| 335 | SDL_free(filebuffer); | ||
| 336 | return; | ||
| 337 | } | ||
| 338 | |||
| 339 | chosen_files_list = new_cfl; | ||
| 340 | chosen_files_list[nfiles] = NULL; | ||
| 341 | chosen_files_list[nfiles - 1] = SDL_strdup(chosen_folder); | ||
| 342 | |||
| 343 | if (!chosen_files_list[nfiles - 1]) { | ||
| 344 | SDL_free(chosen_files_list); | ||
| 345 | callback(userdata, NULL, -1); | ||
| 346 | SDL_free(filebuffer); | ||
| 347 | return; | ||
| 348 | } | ||
| 349 | } | ||
| 350 | |||
| 351 | callback(userdata, (const char * const*) chosen_files_list, getFilterIndex(dialog.nFilterIndex)); | ||
| 352 | |||
| 353 | for (size_t i = 0; i < nfiles; i++) { | ||
| 354 | SDL_free(chosen_files_list[i]); | ||
| 355 | } | ||
| 356 | |||
| 357 | SDL_free(chosen_files_list); | ||
| 358 | } | ||
| 359 | } else { | ||
| 360 | DWORD error = pCommDlgExtendedError(); | ||
| 361 | // Error code 0 means the user clicked the cancel button. | ||
| 362 | if (error == 0) { | ||
| 363 | /* Unlike SDL's handling of errors, Windows does reset the error | ||
| 364 | code to 0 after calling GetOpenFileName if another Windows | ||
| 365 | function before set a different error code, so it's safe to | ||
| 366 | check for success. */ | ||
| 367 | const char *opts[1] = { NULL }; | ||
| 368 | callback(userdata, opts, getFilterIndex(dialog.nFilterIndex)); | ||
| 369 | } else { | ||
| 370 | SDL_SetError("Windows error, CommDlgExtendedError: %ld", pCommDlgExtendedError()); | ||
| 371 | callback(userdata, NULL, -1); | ||
| 372 | } | ||
| 373 | } | ||
| 374 | |||
| 375 | SDL_free(filebuffer); | ||
| 376 | } | ||
| 377 | |||
| 378 | int windows_file_dialog_thread(void *ptr) | ||
| 379 | { | ||
| 380 | windows_ShowFileDialog(ptr); | ||
| 381 | freeWinArgs(ptr); | ||
| 382 | return 0; | ||
| 383 | } | ||
| 384 | |||
| 385 | int CALLBACK browse_callback_proc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData) | ||
| 386 | { | ||
| 387 | switch (uMsg) { | ||
| 388 | case BFFM_INITIALIZED: | ||
| 389 | if (lpData) { | ||
| 390 | SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData); | ||
| 391 | } | ||
| 392 | break; | ||
| 393 | case BFFM_SELCHANGED: | ||
| 394 | break; | ||
| 395 | case BFFM_VALIDATEFAILED: | ||
| 396 | break; | ||
| 397 | default: | ||
| 398 | break; | ||
| 399 | } | ||
| 400 | return 0; | ||
| 401 | } | ||
| 402 | |||
| 403 | void windows_ShowFolderDialog(void *ptr) | ||
| 404 | { | ||
| 405 | winFArgs *args = (winFArgs *) ptr; | ||
| 406 | SDL_Window *window = args->parent; | ||
| 407 | SDL_DialogFileCallback callback = args->callback; | ||
| 408 | void *userdata = args->userdata; | ||
| 409 | HWND parent = NULL; | ||
| 410 | const char *title = args->title; | ||
| 411 | |||
| 412 | if (window) { | ||
| 413 | parent = (HWND) SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); | ||
| 414 | } | ||
| 415 | |||
| 416 | wchar_t *title_w = NULL; | ||
| 417 | |||
| 418 | if (title) { | ||
| 419 | title_w = WIN_UTF8ToStringW(title); | ||
| 420 | if (!title_w) { | ||
| 421 | callback(userdata, NULL, -1); | ||
| 422 | return; | ||
| 423 | } | ||
| 424 | } | ||
| 425 | |||
| 426 | wchar_t buffer[MAX_PATH]; | ||
| 427 | |||
| 428 | BROWSEINFOW dialog; | ||
| 429 | dialog.hwndOwner = parent; | ||
| 430 | dialog.pidlRoot = NULL; | ||
| 431 | dialog.pszDisplayName = buffer; | ||
| 432 | dialog.lpszTitle = title_w; | ||
| 433 | dialog.ulFlags = BIF_USENEWUI; | ||
| 434 | dialog.lpfn = browse_callback_proc; | ||
| 435 | dialog.lParam = (LPARAM)args->default_folder; | ||
| 436 | dialog.iImage = 0; | ||
| 437 | |||
| 438 | LPITEMIDLIST lpItem = SHBrowseForFolderW(&dialog); | ||
| 439 | |||
| 440 | SDL_free(title_w); | ||
| 441 | |||
| 442 | if (lpItem != NULL) { | ||
| 443 | SHGetPathFromIDListW(lpItem, buffer); | ||
| 444 | char *chosen_file = WIN_StringToUTF8W(buffer); | ||
| 445 | const char *files[2] = { chosen_file, NULL }; | ||
| 446 | callback(userdata, (const char * const*) files, -1); | ||
| 447 | SDL_free(chosen_file); | ||
| 448 | } else { | ||
| 449 | const char *files[1] = { NULL }; | ||
| 450 | callback(userdata, (const char * const*) files, -1); | ||
| 451 | } | ||
| 452 | } | ||
| 453 | |||
| 454 | int windows_folder_dialog_thread(void *ptr) | ||
| 455 | { | ||
| 456 | windows_ShowFolderDialog(ptr); | ||
| 457 | freeWinFArgs((winFArgs *)ptr); | ||
| 458 | return 0; | ||
| 459 | } | ||
| 460 | |||
| 461 | wchar_t *win_get_filters(const SDL_DialogFileFilter *filters, int nfilters) | ||
| 462 | { | ||
| 463 | wchar_t *filter_wchar = NULL; | ||
| 464 | |||
| 465 | if (filters) { | ||
| 466 | // '\x01' is used in place of a null byte | ||
| 467 | // suffix needs two null bytes in case the filter list is empty | ||
| 468 | char *filterlist = convert_filters(filters, nfilters, clear_filt_names, | ||
| 469 | "", "", "\x01\x01", "", "\x01", | ||
| 470 | "\x01", "*.", ";*.", ""); | ||
| 471 | |||
| 472 | if (!filterlist) { | ||
| 473 | return NULL; | ||
| 474 | } | ||
| 475 | |||
| 476 | int filter_len = (int)SDL_strlen(filterlist); | ||
| 477 | |||
| 478 | for (char *c = filterlist; *c; c++) { | ||
| 479 | if (*c == '\x01') { | ||
| 480 | *c = '\0'; | ||
| 481 | } | ||
| 482 | } | ||
| 483 | |||
| 484 | int filter_wlen = MultiByteToWideChar(CP_UTF8, 0, filterlist, filter_len, NULL, 0); | ||
| 485 | filter_wchar = (wchar_t *)SDL_malloc(filter_wlen * sizeof(wchar_t)); | ||
| 486 | if (!filter_wchar) { | ||
| 487 | SDL_free(filterlist); | ||
| 488 | return NULL; | ||
| 489 | } | ||
| 490 | |||
| 491 | MultiByteToWideChar(CP_UTF8, 0, filterlist, filter_len, filter_wchar, filter_wlen); | ||
| 492 | |||
| 493 | SDL_free(filterlist); | ||
| 494 | } | ||
| 495 | |||
| 496 | return filter_wchar; | ||
| 497 | } | ||
| 498 | |||
| 499 | static void ShowFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location, bool allow_many, bool is_save, const char *title, const char *accept, const char *cancel) | ||
| 500 | { | ||
| 501 | winArgs *args; | ||
| 502 | SDL_Thread *thread; | ||
| 503 | wchar_t *filters_str; | ||
| 504 | |||
| 505 | if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) { | ||
| 506 | SDL_SetError("File dialog driver unsupported"); | ||
| 507 | callback(userdata, NULL, -1); | ||
| 508 | return; | ||
| 509 | } | ||
| 510 | |||
| 511 | args = (winArgs *)SDL_malloc(sizeof(*args)); | ||
| 512 | if (args == NULL) { | ||
| 513 | callback(userdata, NULL, -1); | ||
| 514 | return; | ||
| 515 | } | ||
| 516 | |||
| 517 | filters_str = win_get_filters(filters, nfilters); | ||
| 518 | |||
| 519 | if (!filters_str && filters) { | ||
| 520 | callback(userdata, NULL, -1); | ||
| 521 | SDL_free(args); | ||
| 522 | return; | ||
| 523 | } | ||
| 524 | |||
| 525 | args->is_save = is_save; | ||
| 526 | args->filters_str = filters_str; | ||
| 527 | args->default_file = default_location ? SDL_strdup(default_location) : NULL; | ||
| 528 | args->parent = window; | ||
| 529 | args->flags = allow_many ? OFN_ALLOWMULTISELECT : 0; | ||
| 530 | args->callback = callback; | ||
| 531 | args->userdata = userdata; | ||
| 532 | args->title = title ? SDL_strdup(title) : NULL; | ||
| 533 | args->accept = accept ? SDL_strdup(accept) : NULL; | ||
| 534 | args->cancel = cancel ? SDL_strdup(cancel) : NULL; | ||
| 535 | |||
| 536 | thread = SDL_CreateThread(windows_file_dialog_thread, "SDL_Windows_ShowFileDialog", (void *) args); | ||
| 537 | |||
| 538 | if (thread == NULL) { | ||
| 539 | callback(userdata, NULL, -1); | ||
| 540 | // The thread won't have run, therefore the data won't have been freed | ||
| 541 | freeWinArgs(args); | ||
| 542 | return; | ||
| 543 | } | ||
| 544 | |||
| 545 | SDL_DetachThread(thread); | ||
| 546 | } | ||
| 547 | |||
| 548 | void ShowFolderDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const char *default_location, bool allow_many, const char *title, const char *accept, const char *cancel) | ||
| 549 | { | ||
| 550 | winFArgs *args; | ||
| 551 | SDL_Thread *thread; | ||
| 552 | |||
| 553 | if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) { | ||
| 554 | SDL_SetError("File dialog driver unsupported"); | ||
| 555 | callback(userdata, NULL, -1); | ||
| 556 | return; | ||
| 557 | } | ||
| 558 | |||
| 559 | args = (winFArgs *)SDL_malloc(sizeof(*args)); | ||
| 560 | if (args == NULL) { | ||
| 561 | callback(userdata, NULL, -1); | ||
| 562 | return; | ||
| 563 | } | ||
| 564 | |||
| 565 | args->parent = window; | ||
| 566 | args->callback = callback; | ||
| 567 | args->default_folder = default_location ? SDL_strdup(default_location) : NULL; | ||
| 568 | args->userdata = userdata; | ||
| 569 | args->title = title ? SDL_strdup(title) : NULL; | ||
| 570 | args->accept = accept ? SDL_strdup(accept) : NULL; | ||
| 571 | args->cancel = cancel ? SDL_strdup(cancel) : NULL; | ||
| 572 | |||
| 573 | thread = SDL_CreateThread(windows_folder_dialog_thread, "SDL_Windows_ShowFolderDialog", (void *) args); | ||
| 574 | |||
| 575 | if (thread == NULL) { | ||
| 576 | callback(userdata, NULL, -1); | ||
| 577 | // The thread won't have run, therefore the data won't have been freed | ||
| 578 | freeWinFArgs(args); | ||
| 579 | return; | ||
| 580 | } | ||
| 581 | |||
| 582 | SDL_DetachThread(thread); | ||
| 583 | } | ||
| 584 | |||
| 585 | void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props) | ||
| 586 | { | ||
| 587 | /* The internal functions will start threads, and the properties may be freed as soon as this function returns. | ||
| 588 | Save a copy of what we need before invoking the functions and starting the threads. */ | ||
| 589 | SDL_Window *window = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL); | ||
| 590 | SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL); | ||
| 591 | int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0); | ||
| 592 | bool allow_many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false); | ||
| 593 | const char *default_location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL); | ||
| 594 | const char *title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, NULL); | ||
| 595 | const char *accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL); | ||
| 596 | const char *cancel = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_CANCEL_STRING, NULL); | ||
| 597 | bool is_save = false; | ||
| 598 | |||
| 599 | switch (type) { | ||
| 600 | case SDL_FILEDIALOG_SAVEFILE: | ||
| 601 | is_save = true; | ||
| 602 | SDL_FALLTHROUGH; | ||
| 603 | case SDL_FILEDIALOG_OPENFILE: | ||
| 604 | ShowFileDialog(callback, userdata, window, filters, nfilters, default_location, allow_many, is_save, title, accept, cancel); | ||
| 605 | break; | ||
| 606 | |||
| 607 | case SDL_FILEDIALOG_OPENFOLDER: | ||
| 608 | ShowFolderDialog(callback, userdata, window, default_location, allow_many, title, accept, cancel); | ||
| 609 | break; | ||
| 610 | }; | ||
| 611 | } | ||
