Skip to content

Commit

Permalink
Merge pull request #87303 from bruvzg/fd_options_no_editor
Browse files Browse the repository at this point in the history
[Native File Dialog] Add support for adding custom options to the dialogs (w/o editor changes).
  • Loading branch information
YuriSizov committed Jan 25, 2024
2 parents 1949d91 + a8f521b commit 672b034
Show file tree
Hide file tree
Showing 17 changed files with 1,178 additions and 201 deletions.
30 changes: 27 additions & 3 deletions doc/classes/DisplayServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,33 @@
<param index="6" name="callback" type="Callable" />
<description>
Displays OS native dialog for selecting files or directories in the file system.
Callbacks have the following arguments: [code]bool status, PackedStringArray selected_paths, int selected_filter_index[/code].
[b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG] feature.
[b]Note:[/b] This method is implemented on Linux, Windows and macOS.
Callbacks have the following arguments: [code]status: bool, selected_paths: PackedStringArray, selected_filter_index: int[/code].
[b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG] feature, i.e. Linux, Windows, and macOS.
[b]Note:[/b] [param current_directory] might be ignored.
[b]Note:[/b] On Linux, [param show_hidden] is ignored.
[b]Note:[/b] On macOS, native file dialogs have no title.
[b]Note:[/b] On macOS, sandboxed apps will save security-scoped bookmarks to retain access to the opened folders across multiple sessions. Use [method OS.get_granted_permissions] to get a list of saved bookmarks.
</description>
</method>
<method name="file_dialog_with_options_show">
<return type="int" enum="Error" />
<param index="0" name="title" type="String" />
<param index="1" name="current_directory" type="String" />
<param index="2" name="root" type="String" />
<param index="3" name="filename" type="String" />
<param index="4" name="show_hidden" type="bool" />
<param index="5" name="mode" type="int" enum="DisplayServer.FileDialogMode" />
<param index="6" name="filters" type="PackedStringArray" />
<param index="7" name="options" type="Dictionary[]" />
<param index="8" name="callback" type="Callable" />
<description>
Displays OS native dialog for selecting files or directories in the file system with additional user selectable options.
[param options] is array of [Dictionary]s with the following keys:
- [code]"name"[/code] - option's name [String].
- [code]"values"[/code] - [PackedStringArray] of values. If empty, boolean option (check box) is used.
- [code]"default"[/code] - default selected option index ([int]) or default boolean value ([bool]).
Callbacks have the following arguments: [code]status: bool, selected_paths: PackedStringArray, selected_filter_index: int, selected_option: Dictionary[/code].
[b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG] feature, i.e. Linux, Windows, and macOS.
[b]Note:[/b] [param current_directory] might be ignored.
[b]Note:[/b] On Linux, [param show_hidden] is ignored.
[b]Note:[/b] On macOS, native file dialogs have no title.
Expand Down
63 changes: 63 additions & 0 deletions doc/classes/FileDialog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@
For example, a [param filter] of [code]"*.png, *.jpg"[/code] and a [param description] of [code]"Images"[/code] results in filter text "Images (*.png, *.jpg)".
</description>
</method>
<method name="add_option">
<return type="void" />
<param index="0" name="name" type="String" />
<param index="1" name="values" type="PackedStringArray" />
<param index="2" name="index" type="int" />
<description>
Adds an additional [OptionButton] to the file dialog. If [param values] is empty, a [CheckBox] is added instead.
</description>
</method>
<method name="clear_filters">
<return type="void" />
<description>
Expand All @@ -38,6 +47,33 @@
[b]Warning:[/b] This is a required internal node, removing and freeing it may cause a crash. If you wish to hide it or any of its children, use their [member CanvasItem.visible] property.
</description>
</method>
<method name="get_option_default" qualifiers="const">
<return type="int" />
<param index="0" name="option" type="int" />
<description>
Returns the default value index of the [OptionButton] or [CheckBox] with index [param option].
</description>
</method>
<method name="get_option_name" qualifiers="const">
<return type="String" />
<param index="0" name="option" type="int" />
<description>
Returns the name of the [OptionButton] or [CheckBox] with index [param option].
</description>
</method>
<method name="get_option_values" qualifiers="const">
<return type="PackedStringArray" />
<param index="0" name="option" type="int" />
<description>
Returns an array of values of the [OptionButton] with index [param option].
</description>
</method>
<method name="get_selected_options" qualifiers="const">
<return type="Dictionary" />
<description>
Returns a [Dictionary] with the selected values of the additional [OptionButton]s and/or [CheckBox]es. [Dictionary] keys are names and values are selected value indices.
</description>
</method>
<method name="get_vbox">
<return type="VBoxContainer" />
<description>
Expand All @@ -51,6 +87,30 @@
Invalidate and update the current dialog content list.
</description>
</method>
<method name="set_option_default">
<return type="void" />
<param index="0" name="option" type="int" />
<param index="1" name="index" type="int" />
<description>
Sets the default value index of the [OptionButton] or [CheckBox] with index [param option].
</description>
</method>
<method name="set_option_name">
<return type="void" />
<param index="0" name="option" type="int" />
<param index="1" name="name" type="String" />
<description>
Sets the name of the [OptionButton] or [CheckBox] with index [param option].
</description>
</method>
<method name="set_option_values">
<return type="void" />
<param index="0" name="option" type="int" />
<param index="1" name="values" type="PackedStringArray" />
<description>
Sets the option values of the [OptionButton] with index [param option].
</description>
</method>
</methods>
<members>
<member name="access" type="int" setter="set_access" getter="get_access" enum="FileDialog.Access" default="0">
Expand All @@ -76,6 +136,9 @@
<member name="mode_overrides_title" type="bool" setter="set_mode_overrides_title" getter="is_mode_overriding_title" default="true">
If [code]true[/code], changing the [member file_mode] property will set the window title accordingly (e.g. setting [member file_mode] to [constant FILE_MODE_OPEN_FILE] will change the window title to "Open a File").
</member>
<member name="option_count" type="int" setter="set_option_count" getter="get_option_count" default="0">
The number of additional [OptionButton]s and [CheckBox]es in the dialog.
</member>
<member name="root_subfolder" type="String" setter="set_root_subfolder" getter="get_root_subfolder" default="&quot;&quot;">
If non-empty, the given sub-folder will be "root" of this [FileDialog], i.e. user won't be able to go to its parent directory.
</member>
Expand Down
113 changes: 102 additions & 11 deletions platform/linuxbsd/freedesktop_portal_desktop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,54 @@ void FreeDesktopPortalDesktop::append_dbus_string(DBusMessageIter *p_iter, const
}
}

void FreeDesktopPortalDesktop::append_dbus_dict_options(DBusMessageIter *p_iter, const TypedArray<Dictionary> &p_options) {
DBusMessageIter dict_iter;
DBusMessageIter var_iter;
DBusMessageIter arr_iter;
const char *choices_key = "choices";

dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter);
dbus_message_iter_append_basic(&dict_iter, DBUS_TYPE_STRING, &choices_key);
dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "a(ssa(ss)s)", &var_iter);
dbus_message_iter_open_container(&var_iter, DBUS_TYPE_ARRAY, "(ss(ss)s)", &arr_iter);

for (int i = 0; i < p_options.size(); i++) {
const Dictionary &item = p_options[i];
if (!item.has("name") || !item.has("values") || !item.has("default")) {
continue;
}
const String &name = item["name"];
const Vector<String> &options = item["values"];
int default_idx = item["default"];

DBusMessageIter struct_iter;
DBusMessageIter array_iter;
DBusMessageIter array_struct_iter;
dbus_message_iter_open_container(&arr_iter, DBUS_TYPE_STRUCT, nullptr, &struct_iter);
append_dbus_string(&struct_iter, name); // ID.
append_dbus_string(&struct_iter, name); // User visible name.

dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(ss)", &array_iter);
for (int j = 0; j < options.size(); j++) {
dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter);
append_dbus_string(&array_struct_iter, itos(j));
append_dbus_string(&array_struct_iter, options[j]);
dbus_message_iter_close_container(&array_iter, &array_struct_iter);
}
dbus_message_iter_close_container(&struct_iter, &array_iter);
if (options.is_empty()) {
append_dbus_string(&struct_iter, (default_idx) ? "true" : "false"); // Default selection.
} else {
append_dbus_string(&struct_iter, itos(default_idx)); // Default selection.
}

dbus_message_iter_close_container(&arr_iter, &struct_iter);
}
dbus_message_iter_close_container(&var_iter, &arr_iter);
dbus_message_iter_close_container(&dict_iter, &var_iter);
dbus_message_iter_close_container(p_iter, &dict_iter);
}

void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filter_names, const Vector<String> &p_filter_exts) {
DBusMessageIter dict_iter;
DBusMessageIter var_iter;
Expand Down Expand Up @@ -223,7 +271,7 @@ void FreeDesktopPortalDesktop::append_dbus_dict_bool(DBusMessageIter *p_iter, co
dbus_message_iter_close_container(p_iter, &dict_iter);
}

bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index) {
bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index, Dictionary &r_options) {
ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_UINT32, false);

dbus_uint32_t resp_code;
Expand Down Expand Up @@ -257,6 +305,34 @@ bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_it
String name = String::utf8(value);

r_index = p_names.find(name);
if (!dbus_message_iter_next(&struct_iter)) {
break;
}
}
}
} else if (strcmp(key, "choices") == 0) { // a(ss) {
if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_ARRAY) {
DBusMessageIter struct_iter;
dbus_message_iter_recurse(&var_iter, &struct_iter);
while (dbus_message_iter_get_arg_type(&struct_iter) == DBUS_TYPE_STRUCT) {
DBusMessageIter opt_iter;
dbus_message_iter_recurse(&struct_iter, &opt_iter);
const char *opt_key = nullptr;
dbus_message_iter_get_basic(&opt_iter, &opt_key);
String opt_skey = String::utf8(opt_key);

dbus_message_iter_next(&opt_iter);
const char *opt_val = nullptr;
dbus_message_iter_get_basic(&opt_iter, &opt_val);
String opt_sval = String::utf8(opt_val);
if (opt_sval == "true") {
r_options[opt_skey] = true;
} else if (opt_sval == "false") {
r_options[opt_skey] = false;
} else {
r_options[opt_skey] = opt_sval.to_int();
}

if (!dbus_message_iter_next(&struct_iter)) {
break;
}
Expand Down Expand Up @@ -285,7 +361,7 @@ bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_it
return true;
}

Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_window_id, const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_window_id, const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb) {
if (unsupported) {
return FAILED;
}
Expand Down Expand Up @@ -322,6 +398,7 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo
fd.callback = p_callback;
fd.prev_focus = p_window_id;
fd.filter_names = filter_names;
fd.opt_in_cb = p_options_in_cb;

CryptoCore::RandomGenerator rng;
ERR_FAIL_COND_V_MSG(rng.init(), FAILED, "Failed to initialize random number generator.");
Expand Down Expand Up @@ -373,6 +450,8 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo
append_dbus_dict_bool(&arr_iter, "multiple", p_mode == DisplayServer::FILE_DIALOG_MODE_OPEN_FILES);
append_dbus_dict_bool(&arr_iter, "directory", p_mode == DisplayServer::FILE_DIALOG_MODE_OPEN_DIR);
append_dbus_dict_filters(&arr_iter, filter_names, filter_exts);

append_dbus_dict_options(&arr_iter, p_options);
append_dbus_dict_string(&arr_iter, "current_folder", p_current_directory, true);
if (p_mode == DisplayServer::FILE_DIALOG_MODE_SAVE_FILE) {
append_dbus_dict_string(&arr_iter, "current_name", p_filename);
Expand Down Expand Up @@ -427,14 +506,25 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo
return OK;
}

void FreeDesktopPortalDesktop::_file_dialog_callback(const Callable &p_callable, const Variant &p_status, const Variant &p_list, const Variant &p_index) {
Variant ret;
Callable::CallError ce;
const Variant *args[3] = { &p_status, &p_list, &p_index };
void FreeDesktopPortalDesktop::_file_dialog_callback(const Callable &p_callable, const Variant &p_status, const Variant &p_list, const Variant &p_index, const Variant &p_options, bool p_opt_in_cb) {
if (p_opt_in_cb) {
Variant ret;
Callable::CallError ce;
const Variant *args[4] = { &p_status, &p_list, &p_index, &p_options };

p_callable.callp(args, 3, ret, ce);
if (ce.error != Callable::CallError::CALL_OK) {
ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callable, args, 3, ce)));
p_callable.callp(args, 4, ret, ce);
if (ce.error != Callable::CallError::CALL_OK) {
ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callable, args, 4, ce)));
}
} else {
Variant ret;
Callable::CallError ce;
const Variant *args[3] = { &p_status, &p_list, &p_index };

p_callable.callp(args, 3, ret, ce);
if (ce.error != Callable::CallError::CALL_OK) {
ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callable, args, 3, ce)));
}
}
}

Expand All @@ -458,11 +548,12 @@ void FreeDesktopPortalDesktop::_thread_file_dialog_monitor(void *p_ud) {
if (dbus_message_iter_init(msg, &iter)) {
bool cancel = false;
Vector<String> uris;
Dictionary options;
int index = 0;
file_chooser_parse_response(&iter, fd.filter_names, cancel, uris, index);
file_chooser_parse_response(&iter, fd.filter_names, cancel, uris, index, options);

if (fd.callback.is_valid()) {
callable_mp(portal, &FreeDesktopPortalDesktop::_file_dialog_callback).call_deferred(fd.callback, !cancel, uris, index);
callable_mp(portal, &FreeDesktopPortalDesktop::_file_dialog_callback).call_deferred(fd.callback, !cancel, uris, index, options, fd.opt_in_cb);
}
if (fd.prev_focus != DisplayServer::INVALID_WINDOW_ID) {
callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(fd.prev_focus);
Expand Down
8 changes: 5 additions & 3 deletions platform/linuxbsd/freedesktop_portal_desktop.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,21 @@ class FreeDesktopPortalDesktop : public Object {
bool read_setting(const char *p_namespace, const char *p_key, int p_type, void *r_value);

static void append_dbus_string(DBusMessageIter *p_iter, const String &p_string);
static void append_dbus_dict_options(DBusMessageIter *p_iter, const TypedArray<Dictionary> &p_options);
static void append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filter_names, const Vector<String> &p_filter_exts);
static void append_dbus_dict_string(DBusMessageIter *p_iter, const String &p_key, const String &p_value, bool p_as_byte_array = false);
static void append_dbus_dict_bool(DBusMessageIter *p_iter, const String &p_key, bool p_value);
static bool file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index);
static bool file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index, Dictionary &r_options);

void _file_dialog_callback(const Callable &p_callable, const Variant &p_status, const Variant &p_list, const Variant &p_index);
void _file_dialog_callback(const Callable &p_callable, const Variant &p_status, const Variant &p_list, const Variant &p_index, const Variant &p_options, bool p_opt_in_cb);

struct FileDialogData {
Vector<String> filter_names;
DBusConnection *connection = nullptr;
DisplayServer::WindowID prev_focus = DisplayServer::INVALID_WINDOW_ID;
Callable callback;
String path;
bool opt_in_cb = false;
};

Mutex file_dialog_mutex;
Expand All @@ -77,7 +79,7 @@ class FreeDesktopPortalDesktop : public Object {

bool is_supported() { return !unsupported; }

Error file_dialog_show(DisplayServer::WindowID p_window_id, const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback);
Error file_dialog_show(DisplayServer::WindowID p_window_id, const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb);

// Retrieve the system's preferred color scheme.
// 0: No preference or unknown.
Expand Down
13 changes: 12 additions & 1 deletion platform/linuxbsd/x11/display_server_x11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,18 @@ Error DisplayServerX11::file_dialog_show(const String &p_title, const String &p_
}

String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window);
return portal_desktop->file_dialog_show(last_focused_window, xid, p_title, p_current_directory, p_filename, p_mode, p_filters, p_callback);
return portal_desktop->file_dialog_show(last_focused_window, xid, p_title, p_current_directory, String(), p_filename, p_mode, p_filters, TypedArray<Dictionary>(), p_callback, false);
}

Error DisplayServerX11::file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) {
WindowID window_id = last_focused_window;

if (!windows.has(window_id)) {
window_id = MAIN_WINDOW_ID;
}

String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window);
return portal_desktop->file_dialog_show(last_focused_window, xid, p_title, p_current_directory, p_root, p_filename, p_mode, p_filters, p_options, p_callback, true);
}

#endif
Expand Down
1 change: 1 addition & 0 deletions platform/linuxbsd/x11/display_server_x11.h
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ class DisplayServerX11 : public DisplayServer {
virtual bool is_dark_mode() const override;

virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) override;
virtual Error file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) override;
#endif

virtual void mouse_set_mode(MouseMode p_mode) override;
Expand Down
1 change: 1 addition & 0 deletions platform/macos/SCsub
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ files = [
"godot_main_macos.mm",
"godot_menu_delegate.mm",
"godot_menu_item.mm",
"godot_open_save_delegate.mm",
"dir_access_macos.mm",
"tts_macos.mm",
"joypad_macos.cpp",
Expand Down
Loading

0 comments on commit 672b034

Please sign in to comment.