SDL: SDL_ShowFileDialogWithProperties with more options

From a4852f3a108531219abd289028d8417f66b39ddb Mon Sep 17 00:00:00 2001
From: Semphris <[EMAIL REDACTED]>
Date: Sat, 12 Oct 2024 00:30:23 -0400
Subject: [PATCH] SDL_ShowFileDialogWithProperties with more options

---
 Android.mk                             |   1 +
 CMakeLists.txt                         |   1 +
 VisualC-GDK/SDL/SDL.vcxproj            |   1 +
 VisualC-GDK/SDL/SDL.vcxproj.filters    |   3 +
 VisualC/SDL/SDL.vcxproj                |   1 +
 VisualC/SDL/SDL.vcxproj.filters        |   3 +
 include/SDL3/SDL_dialog.h              |  94 ++++++++++--
 src/dialog/SDL_dialog.c                | 103 ++++++++++++++
 src/dialog/SDL_dialog.h                |  22 +++
 src/dialog/android/SDL_androiddialog.c |  39 +++--
 src/dialog/cocoa/SDL_cocoadialog.m     |  49 +++----
 src/dialog/dummy/SDL_dummydialog.c     |  16 +--
 src/dialog/haiku/SDL_haikudialog.cc    |  66 ++++++---
 src/dialog/unix/SDL_portaldialog.c     |  79 +++++-----
 src/dialog/unix/SDL_portaldialog.h     |   4 +-
 src/dialog/unix/SDL_unixdialog.c       |  51 ++-----
 src/dialog/unix/SDL_zenitydialog.c     | 190 ++++++++++++++-----------
 src/dialog/unix/SDL_zenitydialog.h     |   4 +-
 src/dialog/windows/SDL_windowsdialog.c | 158 +++++++++++++-------
 19 files changed, 598 insertions(+), 287 deletions(-)
 create mode 100644 src/dialog/SDL_dialog.c
 create mode 100644 src/dialog/SDL_dialog.h

diff --git a/Android.mk b/Android.mk
index 6735c877a98f9..56c817a2b62bf 100644
--- a/Android.mk
+++ b/Android.mk
@@ -30,6 +30,7 @@ LOCAL_SRC_FILES := \
 	$(wildcard $(LOCAL_PATH)/src/core/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/core/android/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/cpuinfo/*.c) \
+	$(LOCAL_PATH)/src/dialog/SDL_dialog.c \
 	$(LOCAL_PATH)/src/dialog/SDL_dialog_utils.c \
 	$(LOCAL_PATH)/src/dialog/android/SDL_androiddialog.c \
 	$(wildcard $(LOCAL_PATH)/src/dynapi/*.c) \
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d7bcfea28fd58..d60fca5ef2454 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2874,6 +2874,7 @@ endif()
 
 if (SDL_DIALOG)
   sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/SDL_dialog_utils.c)
+  sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/SDL_dialog.c)
   if(ANDROID)
     sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/android/SDL_androiddialog.c)
     set(HAVE_SDL_DIALOG TRUE)
diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj
index 34a9ae48817d4..f6a119e178e8c 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj
+++ b/VisualC-GDK/SDL/SDL.vcxproj
@@ -517,6 +517,7 @@
     </ClCompile>
     <ClCompile Include="..\..\src\camera\dummy\SDL_camera_dummy.c" />
     <ClCompile Include="..\..\src\camera\SDL_camera.c" />
+    <ClCompile Include="..\..\src\dialog\SDL_dialog.c" />
     <ClCompile Include="..\..\src\dialog\SDL_dialog_utils.c" />
     <ClCompile Include="..\..\src\filesystem\SDL_filesystem.c" />
     <ClCompile Include="..\..\src\filesystem\windows\SDL_sysfsops.c" />
diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters
index 64cacdc1f8f0b..8bd8a220acb7e 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj.filters
+++ b/VisualC-GDK/SDL/SDL.vcxproj.filters
@@ -7,6 +7,9 @@
     <ClCompile Include="..\..\src\dialog\SDL_dialog_utils.c">
       <Filter>dialog</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\dialog\SDL_dialog.c">
+      <Filter>dialog</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\filesystem\SDL_filesystem.c">
       <Filter>filesystem</Filter>
     </ClCompile>
diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj
index 19da967fd3579..a22ac35d97d99 100644
--- a/VisualC/SDL/SDL.vcxproj
+++ b/VisualC/SDL/SDL.vcxproj
@@ -412,6 +412,7 @@
     <ClCompile Include="..\..\src\camera\dummy\SDL_camera_dummy.c" />
     <ClCompile Include="..\..\src\camera\mediafoundation\SDL_camera_mediafoundation.c" />
     <ClCompile Include="..\..\src\camera\SDL_camera.c" />
+    <ClCompile Include="..\..\src\dialog\SDL_dialog.c" />
     <ClCompile Include="..\..\src\dialog\SDL_dialog_utils.c" />
     <ClCompile Include="..\..\src\filesystem\SDL_filesystem.c" />
     <ClCompile Include="..\..\src\filesystem\windows\SDL_sysfsops.c" />
diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters
index 5e6bd30ff12ab..5dc12b0eaef99 100644
--- a/VisualC/SDL/SDL.vcxproj.filters
+++ b/VisualC/SDL/SDL.vcxproj.filters
@@ -950,6 +950,9 @@
     <ClCompile Include="..\..\src\camera\SDL_camera.c">
       <Filter>camera</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\dialog\SDL_dialog.c">
+      <Filter>dialog</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\dialog\SDL_dialog_utils.c">
       <Filter>dialog</Filter>
     </ClCompile>
diff --git a/include/SDL3/SDL_dialog.h b/include/SDL3/SDL_dialog.h
index 21b022c5843d2..f59fcabd80f4e 100644
--- a/include/SDL3/SDL_dialog.h
+++ b/include/SDL3/SDL_dialog.h
@@ -30,6 +30,7 @@
 
 #include <SDL3/SDL_stdinc.h>
 #include <SDL3/SDL_error.h>
+#include <SDL3/SDL_properties.h>
 #include <SDL3/SDL_video.h>
 
 #include <SDL3/SDL_begin_code.h>
@@ -55,6 +56,7 @@ extern "C" {
  * \sa SDL_ShowOpenFileDialog
  * \sa SDL_ShowSaveFileDialog
  * \sa SDL_ShowOpenFolderDialog
+ * \sa SDL_ShowFileDialogWithProperties
  */
 typedef struct SDL_DialogFileFilter
 {
@@ -93,14 +95,13 @@ typedef struct SDL_DialogFileFilter
  * \sa SDL_ShowOpenFileDialog
  * \sa SDL_ShowSaveFileDialog
  * \sa SDL_ShowOpenFolderDialog
+ * \sa SDL_ShowFileDialogWithProperties
  */
 typedef void (SDLCALL *SDL_DialogFileCallback)(void *userdata, const char * const *filelist, int filter);
 
 /**
  * Displays a dialog that lets the user select a file on their filesystem.
  *
- * This function should only be invoked from the main thread.
- *
  * This is an asynchronous function; it will return immediately, and the
  * result will be passed to the callback.
  *
@@ -127,19 +128,25 @@ typedef void (SDLCALL *SDL_DialogFileCallback)(void *userdata, const char * cons
  *               Not all platforms support this option.
  * \param filters a list of filters, may be NULL. Not all platforms support
  *                this option, and platforms that do support it may allow the
- *                user to ignore the filters.
+ *                user to ignore the filters. If non-NULL, it must remain valid
+ *                at least until the callback is invoked.
  * \param nfilters the number of filters. Ignored if filters is NULL.
  * \param default_location the default folder or file to start the dialog at,
  *                         may be NULL. Not all platforms support this option.
  * \param allow_many if non-zero, the user will be allowed to select multiple
  *                   entries. Not all platforms support this option.
  *
+ * \threadsafety This function should be called only from the main thread. The
+ *               callback may be invoked from the same thread or from a
+ *               different one, depending on the OS's constraints.
+ *
  * \since This function is available since SDL 3.1.3.
  *
  * \sa SDL_DialogFileCallback
  * \sa SDL_DialogFileFilter
  * \sa SDL_ShowSaveFileDialog
  * \sa SDL_ShowOpenFolderDialog
+ * \sa SDL_ShowFileDialogWithProperties
  */
 extern SDL_DECLSPEC void SDLCALL SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location, bool allow_many);
 
@@ -147,8 +154,6 @@ extern SDL_DECLSPEC void SDLCALL SDL_ShowOpenFileDialog(SDL_DialogFileCallback c
  * Displays a dialog that lets the user choose a new or existing file on their
  * filesystem.
  *
- * This function should only be invoked from the main thread.
- *
  * This is an asynchronous function; it will return immediately, and the
  * result will be passed to the callback.
  *
@@ -174,25 +179,29 @@ extern SDL_DECLSPEC void SDLCALL SDL_ShowOpenFileDialog(SDL_DialogFileCallback c
  *               Not all platforms support this option.
  * \param filters a list of filters, may be NULL. Not all platforms support
  *                this option, and platforms that do support it may allow the
- *                user to ignore the filters.
+ *                user to ignore the filters. If non-NULL, it must remain valid
+ *                at least until the callback is invoked.
  * \param nfilters the number of filters. Ignored if filters is NULL.
  * \param default_location the default folder or file to start the dialog at,
  *                         may be NULL. Not all platforms support this option.
  *
+ * \threadsafety This function should be called only from the main thread. The
+ *               callback may be invoked from the same thread or from a
+ *               different one, depending on the OS's constraints.
+ *
  * \since This function is available since SDL 3.1.3.
  *
  * \sa SDL_DialogFileCallback
  * \sa SDL_DialogFileFilter
  * \sa SDL_ShowOpenFileDialog
  * \sa SDL_ShowOpenFolderDialog
+ * \sa SDL_ShowFileDialogWithProperties
  */
 extern SDL_DECLSPEC void SDLCALL SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location);
 
 /**
  * Displays a dialog that lets the user select a folder on their filesystem.
  *
- * This function should only be invoked from the main thread.
- *
  * This is an asynchronous function; it will return immediately, and the
  * result will be passed to the callback.
  *
@@ -222,14 +231,83 @@ extern SDL_DECLSPEC void SDLCALL SDL_ShowSaveFileDialog(SDL_DialogFileCallback c
  * \param allow_many if non-zero, the user will be allowed to select multiple
  *                   entries. Not all platforms support this option.
  *
+ * \threadsafety This function should be called only from the main thread. The
+ *               callback may be invoked from the same thread or from a
+ *               different one, depending on the OS's constraints.
+ *
  * \since This function is available since SDL 3.1.3.
  *
  * \sa SDL_DialogFileCallback
  * \sa SDL_ShowOpenFileDialog
  * \sa SDL_ShowSaveFileDialog
+ * \sa SDL_ShowFileDialogWithProperties
  */
 extern SDL_DECLSPEC void SDLCALL SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const char *default_location, bool allow_many);
 
+typedef enum SDL_FileDialogType {
+  SDL_FILEDIALOG_OPENFILE,
+  SDL_FILEDIALOG_SAVEFILE,
+  SDL_FILEDIALOG_OPENFOLDER
+} SDL_FileDialogType;
+
+#define SDL_PROP_FILE_DIALOG_FILTERS_POINTER     "SDL.filedialog.filters"
+#define SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER     "SDL.filedialog.nfilters"
+#define SDL_PROP_FILE_DIALOG_WINDOW_POINTER      "SDL.filedialog.window"
+#define SDL_PROP_FILE_DIALOG_LOCATION_STRING     "SDL.filedialog.location"
+#define SDL_PROP_FILE_DIALOG_MANY_BOOLEAN        "SDL.filedialog.many"
+#define SDL_PROP_FILE_DIALOG_TITLE_STRING        "SDL.filedialog.title"
+#define SDL_PROP_FILE_DIALOG_ACCEPT_STRING       "SDL.filedialog.accept"
+#define SDL_PROP_FILE_DIALOG_CANCEL_STRING       "SDL.filedialog.cancel"
+
+/**
+ * Create and launch a file dialog with the specified properties.
+ *
+ * These are the supported properties:
+ *
+ * - `SDL_PROP_FILE_DIALOG_FILTERS_POINTER`: a pointer to a list of
+ *   SDL_DialogFileFilter's, which will be used as filters for file-based
+ *   selections. Ignored if the dialog is an "Open Folder" dialog. If non-NULL,
+ *   the array of filters must remain valid at least until the callback is
+ *   invoked.
+ * - `SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER`: the number of filters in the array
+ *   of filters, if it exists.
+ * - `SDL_PROP_FILE_DIALOG_WINDOW_POINTER`: the window that the dialog should
+ *   be modal for.
+ * - `SDL_PROP_FILE_DIALOG_LOCATION_STRING`: the default folder or file to
+ *   start the dialog at.
+ * - `SDL_PROP_FILE_DIALOG_MANY_BOOLEAN`: true to allow the user to select more
+ *   than one entry.
+ * - `SDL_PROP_FILE_DIALOG_TITLE_STRING`: the title for the dialog.
+ * - `SDL_PROP_FILE_DIALOG_ACCEPT_STRING`: the label that the accept button
+ *   should have.
+ * - `SDL_PROP_FILE_DIALOG_CANCEL_STRING`: the label that the cancel button
+ *   should have.
+ *
+ * Note that each platform may or may not support any of the properties.
+ *
+ * \param type the type of file dialog.
+ * \param callback a function pointer to be invoked when the user selects a
+ *                 file and accepts, or cancels the dialog, or an error
+ *                 occurs.
+ * \param userdata an optional pointer to pass extra data to the callback when
+ *                 it will be invoked.
+ * \param props the properties to use.
+ *
+ * \threadsafety This function should be called only from the main thread. The
+ *               callback may be invoked from the same thread or from a
+ *               different one, depending on the OS's constraints.
+ *
+ * \since This function is available since SDL 3.2.0.
+ *
+ * \sa SDL_FileDialogType
+ * \sa SDL_DialogFileCallback
+ * \sa SDL_DialogFileFilter
+ * \sa SDL_ShowOpenFileDialog
+ * \sa SDL_ShowSaveFileDialog
+ * \sa SDL_ShowOpenFolderDialog
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props);
+
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
 }
diff --git a/src/dialog/SDL_dialog.c b/src/dialog/SDL_dialog.c
new file mode 100644
index 0000000000000..938666b210f9c
--- /dev/null
+++ b/src/dialog/SDL_dialog.c
@@ -0,0 +1,103 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "SDL_internal.h"
+
+#include "SDL_dialog.h"
+#include "SDL_dialog_utils.h"
+
+void SDL_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
+{
+    if (!callback) {
+        return;
+    }
+
+    SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL);
+    int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, -1);
+
+    if (filters && nfilters == -1) {
+        SDL_SetError("Set filter pointers, but didn't set number of filters (SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER)");
+        callback(userdata, NULL, -1);
+        return;
+    }
+
+    const char *msg = validate_filters(filters, nfilters);
+
+    if (msg) {
+        SDL_SetError("Invalid dialog file filters: %s", msg);
+        callback(userdata, NULL, -1);
+        return;
+    }
+
+    switch (type) {
+    case SDL_FILEDIALOG_OPENFILE:
+    case SDL_FILEDIALOG_SAVEFILE:
+    case SDL_FILEDIALOG_OPENFOLDER:
+        SDL_SYS_ShowFileDialogWithProperties(type, callback, userdata, props);
+        break;
+
+    default:
+        SDL_SetError("Unsupported file dialog type: %d", (int) type);
+        callback(userdata, NULL, -1);
+        break;
+    };
+}
+
+void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location, bool allow_many)
+{
+    SDL_PropertiesID props = SDL_CreateProperties();
+
+    SDL_SetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, (void *) filters);
+    SDL_SetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, nfilters);
+    SDL_SetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, window);
+    SDL_SetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, default_location);
+    SDL_SetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, allow_many);
+
+    SDL_ShowFileDialogWithProperties(SDL_FILEDIALOG_OPENFILE, callback, userdata, props);
+
+    SDL_DestroyProperties(props);
+}
+
+void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location)
+{
+    SDL_PropertiesID props = SDL_CreateProperties();
+
+    SDL_SetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, (void *) filters);
+    SDL_SetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, nfilters);
+    SDL_SetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, window);
+    SDL_SetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, default_location);
+
+    SDL_ShowFileDialogWithProperties(SDL_FILEDIALOG_SAVEFILE, callback, userdata, props);
+
+    SDL_DestroyProperties(props);
+}
+
+void SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const char *default_location, bool allow_many)
+{
+    SDL_PropertiesID props = SDL_CreateProperties();
+
+    SDL_SetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, window);
+    SDL_SetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, default_location);
+    SDL_SetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, allow_many);
+
+    SDL_ShowFileDialogWithProperties(SDL_FILEDIALOG_OPENFOLDER, callback, userdata, props);
+
+    SDL_DestroyProperties(props);
+}
diff --git a/src/dialog/SDL_dialog.h b/src/dialog/SDL_dialog.h
new file mode 100644
index 0000000000000..b3d3318f33ce4
--- /dev/null
+++ b/src/dialog/SDL_dialog.h
@@ -0,0 +1,22 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props);
diff --git a/src/dialog/android/SDL_androiddialog.c b/src/dialog/android/SDL_androiddialog.c
index fd9e102ef64f5..d4723f843b070 100644
--- a/src/dialog/android/SDL_androiddialog.c
+++ b/src/dialog/android/SDL_androiddialog.c
@@ -20,26 +20,39 @@
 */
 
 #include "SDL_internal.h"
+#include "../SDL_dialog.h"
 #include "../../core/android/SDL_android.h"
 
-void SDLCALL SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location, bool allow_many)
+void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
 {
-    if (!Android_JNI_OpenFileDialog(callback, userdata, filters, nfilters, false, allow_many)) {
-        // SDL_SetError is already called when it fails
+    SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL);
+    int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0);
+    bool allow_many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false);
+    bool is_save;
+
+    if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) {
+        SDL_SetError("File dialog driver unsupported (don't set SDL_HINT_FILE_DIALOG_DRIVER)");
         callback(userdata, NULL, -1);
+        return;
     }
-}
 
-void SDLCALL SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location)
-{
-    if (!Android_JNI_OpenFileDialog(callback, userdata, filters, nfilters, true, false)) {
+    switch (type) {
+    case SDL_FILEDIALOG_OPENFILE:
+        is_save = false;
+        break;
+
+    case SDL_FILEDIALOG_SAVEFILE:
+        is_save = true;
+        break;
+
+    case SDL_FILEDIALOG_OPENFOLDER:
+        SDL_Unsupported();
+        callback(userdata, NULL, -1);
+        return;
+    };
+
+    if (!Android_JNI_OpenFileDialog(callback, userdata, filters, nfilters, is_save, allow_many)) {
         // SDL_SetError is already called when it fails
         callback(userdata, NULL, -1);
     }
 }
-
-void SDLCALL SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const char *default_location, bool allow_many)
-{
-    SDL_Unsupported();
-    callback(userdata, NULL, -1);
-}
diff --git a/src/dialog/cocoa/SDL_cocoadialog.m b/src/dialog/cocoa/SDL_cocoadialog.m
index 2500663735341..22f67b6c54f00 100644
--- a/src/dialog/cocoa/SDL_cocoadialog.m
+++ b/src/dialog/cocoa/SDL_cocoadialog.m
@@ -19,6 +19,7 @@
   3. This notice may not be removed or altered from any source distribution.
 */
 #include "SDL_internal.h"
+#include "../SDL_dialog.h"
 #include "../SDL_dialog_utils.h"
 
 #ifdef SDL_PLATFORM_MACOS
@@ -26,15 +27,16 @@
 #import <Cocoa/Cocoa.h>
 #import <UniformTypeIdentifiers/UTType.h>
 
-typedef enum
+void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
 {
-    FDT_SAVE,
-    FDT_OPEN,
-    FDT_OPENFOLDER
-} cocoa_FileDialogType;
+    SDL_Window* window = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL);
+    SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL);
+    int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0);
+    bool allow_many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false);
+    const char* default_location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL);
+    const char* title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, NULL);
+    const char* accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL);
 
-void show_file_dialog(cocoa_FileDialogType type, SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location, bool allow_many)
-{
     if (filters) {
         const char *msg = validate_filters(filters, nfilters);
 
@@ -46,7 +48,7 @@ void show_file_dialog(cocoa_FileDialogType type, SDL_DialogFileCallback callback
     }
 
     if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) {
-        SDL_SetError("File dialog driver unsupported");
+        SDL_SetError("File dialog driver unsupported (don't set SDL_HINT_FILE_DIALOG_DRIVER)");
         callback(userdata, NULL, -1);
         return;
     }
@@ -56,15 +58,17 @@ void show_file_dialog(cocoa_FileDialogType type, SDL_DialogFileCallback callback
     NSOpenPanel *dialog_as_open;
 
     switch (type) {
-    case FDT_SAVE:
+    case SDL_FILEDIALOG_SAVEFILE:
         dialog = [NSSavePanel savePanel];
         break;
-    case FDT_OPEN:
+
+    case SDL_FILEDIALOG_OPENFILE:
         dialog_as_open = [NSOpenPanel openPanel];
         [dialog_as_open setAllowsMultipleSelection:((allow_many == true) ? YES : NO)];
         dialog = dialog_as_open;
         break;
-    case FDT_OPENFOLDER:
+
+    case SDL_FILEDIALOG_OPENFOLDER:
         dialog_as_open = [NSOpenPanel openPanel];
         [dialog_as_open setCanChooseFiles:NO];
         [dialog_as_open setCanChooseDirectories:YES];
@@ -73,6 +77,14 @@ void show_file_dialog(cocoa_FileDialogType type, SDL_DialogFileCallback callback
         break;
     };
 
+    if (title) {
+        [dialog setTitle:[NSString stringWithUTF8String:title]];
+    }
+
+    if (accept) {
+        [dialog setPrompt:[NSString stringWithUTF8String:accept]];
+    }
+
     if (filters) {
         // On macOS 11.0 and up, this is an array of UTType. Prior to that, it's an array of NSString
         NSMutableArray *types = [[NSMutableArray alloc] initWithCapacity:nfilters ];
@@ -175,19 +187,4 @@ void show_file_dialog(cocoa_FileDialogType type, SDL_DialogFileCallback callback
     }
 }
 
-void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location, bool allow_many)
-{
-    show_file_dialog(FDT_OPEN, callback, userdata, window, filters, nfilters, default_location, allow_many);
-}
-
-void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location)
-{
-    show_file_dialog(FDT_SAVE, callback, userdata, window, filters, nfilters, default_location, 0);
-}
-
-void SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, bool allow_many)
-{
-    show_file_dialog(FDT_OPENFOLDER, callback, userdata, window, NULL, 0, default_location, allow_many);
-}
-
 #endif // SDL_PLATFORM_MACOS
diff --git a/src/dialog/dummy/SDL_dummydialog.c b/src/dialog/dummy/SDL_dummydialog.c
index 81ba1f59d98ed..16cf1802d33ea 100644
--- a/src/dialog/dummy/SDL_dummydialog.c
+++ b/src/dialog/dummy/SDL_dummydialog.c
@@ -20,21 +20,11 @@
 */
 #include "SDL_internal.h"
 
-#ifdef SDL_DIALOG_DUMMY
-
-void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location, bool allow_many)
-{
-  SDL_Unsupported();
-  callback(userdata, NULL, -1);
-}
+#include "../SDL_dialog.h"
 
-void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, int nfilters, const char* default_location)
-{
-  SDL_Unsupported();
-  callback(userdata, NULL, -1);
-}
+#ifdef SDL_DIALOG_DUMMY
 
-void SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, bool allow_many)
+void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
 {
   SDL_Unsupported();
   callback(userdata, NULL, -1);
diff --git a/src/dialog/haiku/SDL_haikudialog.cc b/src/dialog/haiku/SDL_haikudialog.cc
index 9d6e37303e4e2..1ebf000a17288 100644
--- a/src/dialog/haiku/SDL_haikudialog.cc
+++ b/src/dialog/haiku/SDL_haikudialog.cc
@@ -20,9 +20,11 @@
 */
 #include "SDL_internal.h"
 extern "C" {
+#include "../SDL_dialog.h"
 #include "../SDL_dialog_utils.h"
 }
 #include "../../core/haiku/SDL_BeApp.h"
+#include "../../video/haiku/SDL_BWin.h"
 
 #include <string>
 #include <vector>
@@ -190,8 +192,35 @@ class CallbackLooper : public BLooper
     SDLBRefFilter *m_filter;
 };
 
-void ShowDialog(bool save, SDL_DialogFileCallback callback, void *userdata, bool many, bool modal, const SDL_DialogFileFilter *filters, int nfilters, bool folder, const char *location)
+void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
 {
+    SDL_Window* window = (SDL_Window*) SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL);
+    SDL_DialogFileFilter* filters = (SDL_DialogFileFilter*) SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL);
+    int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0);
+    bool many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false);
+    const char* location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL);
+    const char* title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, NULL);
+    const char* accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL);
+    const char* cancel = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_CANCEL_STRING, NULL);
+
+    bool modal = !!window;
+
+    bool save = false;
+    bool folder = false;
+
+    switch (type) {
+    case SDL_FILEDIALOG_SAVEFILE:
+        save = true;
+        break;
+
+    case SDL_FILEDIALOG_OPENFILE:
+        break;
+
+    case SDL_FILEDIALOG_OPENFOLDER:
+        folder = true;
+        break;
+    };
+
     if (!SDL_InitBeApp()) {
         char* err = SDL_strdup(SDL_GetError());
         SDL_SetError("Couldn't init Be app: %s", err);
@@ -238,22 +267,27 @@ void ShowDialog(bool save, SDL_DialogFileCallback callback, void *userdata, bool
     }
 
     BFilePanel *panel = new BFilePanel(save ? B_SAVE_PANEL : B_OPEN_PANEL, messenger, location ? &entryref : NULL, folder ? B_DIRECTORY_NODE : B_FILE_NODE, many, NULL, filter, modal);
-    looper->SetToBeFreed(messenger, panel, filter);
-    looper->Run();
-    panel->Show();
-}
 
-void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location, bool allow_many)
-{
-    ShowDialog(false, callback, userdata, allow_many == true, !!window, filters, nfilters, false, default_location);
-}
+    if (title) {
+        panel->Window()->SetTitle(title);
+    }
 
-void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location)
-{
-    ShowDialog(true, callback, userdata, false, !!window, filters, nfilters, false, default_location);
-}
+    if (accept) {
+        panel->SetButtonLabel(B_DEFAULT_BUTTON, accept);
+    }
 
-void SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const char* default_location, bool allow_many)
-{
-    ShowDialog(false, callback, userdata, allow_many == true, !!window, NULL, 0, true, default_location);
+    if (cancel) {
+        panel->SetButtonLabel(B_CANCEL_BUTTON, cancel);
+    }
+
+    if (window) {
+        SDL_BWin *bwin = (SDL_BWin *)(window->internal);
+        panel->Window()->SetLook(B_MODAL_WINDOW_LOOK);
+        panel->Window()->SetFeel(B_MODAL_SUBSET_WINDOW_FEEL);
+        panel->Window()->AddToSubset(bwin);
+    }
+
+    looper->SetToBeFreed(messenger, panel, filter);
+    looper->Run();
+    panel->Show();
 }
diff --git a/src/dialog/unix/SDL_portaldialog.c b/src/dialog/unix/SDL_portaldialog.c
index ac6b575c49fe2..2d0567dd558eb 100644
--- a/src/dialog/unix/SDL_portaldialog.c
+++ b/src/dialog/unix/SDL_portaldialog.c
@@ -275,8 +275,43 @@ static DBusHandlerResult DBus_MessageFilter(DBusConnection *conn, DBusMessage *m
     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 }
 
-static void DBus_OpenDialog(const char *method, const char *method_title, SDL_DialogFileCallback callback, void* userdat

(Patch may be truncated, please check the link at the top of this post.)