From 5ab9e50461d02aaa5277abb4a7ffe84e5eeb2509 Mon Sep 17 00:00:00 2001 From: kobewi Date: Tue, 13 Sep 2022 19:07:25 +0200 Subject: [PATCH] Add a dialog to customize run instances --- editor/editor_run.cpp | 107 +------- editor/editor_run.h | 2 - editor/plugins/debugger_editor_plugin.cpp | 39 +-- editor/plugins/debugger_editor_plugin.h | 5 +- editor/run_instances_dialog.cpp | 299 ++++++++++++++++++++++ editor/run_instances_dialog.h | 78 ++++++ 6 files changed, 404 insertions(+), 126 deletions(-) create mode 100644 editor/run_instances_dialog.cpp create mode 100644 editor/run_instances_dialog.h diff --git a/editor/editor_run.cpp b/editor/editor_run.cpp index 00db344b6a9..33395c8712f 100644 --- a/editor/editor_run.cpp +++ b/editor/editor_run.cpp @@ -34,48 +34,10 @@ #include "editor/debugger/editor_debugger_node.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" +#include "editor/run_instances_dialog.h" #include "main/main.h" #include "servers/display_server.h" -/** - * Separates command line arguments without splitting up quoted strings. - */ -Vector EditorRun::_split_cmdline_args(const String &arg_string) { - Vector split_args; - int arg_start = 0; - bool is_quoted = false; - char32_t quote_char = '-'; - char32_t arg_char; - int arg_length; - for (int i = 0; i < arg_string.length(); i++) { - arg_char = arg_string[i]; - if (arg_char == '\"' || arg_char == '\'') { - if (i == 0 || arg_string[i - 1] != '\\') { - if (is_quoted) { - if (arg_char == quote_char) { - is_quoted = false; - quote_char = '-'; - } - } else { - is_quoted = true; - quote_char = arg_char; - } - } - } else if (!is_quoted && arg_char == ' ') { - arg_length = i - arg_start; - if (arg_length > 0) { - split_args.push_back(arg_string.substr(arg_start, arg_length)); - } - arg_start = i + 1; - } - } - arg_length = arg_string.length() - arg_start; - if (arg_length > 0) { - split_args.push_back(arg_string.substr(arg_start, arg_length)); - } - return split_args; -} - EditorRun::Status EditorRun::get_status() const { return status; } @@ -261,67 +223,26 @@ Error EditorRun::run(const String &p_scene, const String &p_write_movie) { args.push_back(p_scene); } - String exec = OS::get_singleton()->get_executable_path(); - - const String raw_custom_args = GLOBAL_GET("editor/run/main_run_args"); - if (!raw_custom_args.is_empty()) { - // Allow the user to specify a command to run, similar to Steam's launch options. - // In this case, Godot will no longer be run directly; it's up to the underlying command - // to run it. For instance, this can be used on Linux to force a running project - // to use Optimus using `prime-run` or similar. - // Example: `prime-run %command% --time-scale 0.5` - const int placeholder_pos = raw_custom_args.find("%command%"); - - Vector custom_args; - - if (placeholder_pos != -1) { - // Prepend executable-specific custom arguments. - // If nothing is placed before `%command%`, behave as if no placeholder was specified. - Vector exec_args = _split_cmdline_args(raw_custom_args.substr(0, placeholder_pos)); - if (exec_args.size() >= 1) { - exec = exec_args[0]; - exec_args.remove_at(0); - - // Append the Godot executable name before we append executable arguments - // (since the order is reversed when using `push_front()`). - args.push_front(OS::get_singleton()->get_executable_path()); - } - - for (int i = exec_args.size() - 1; i >= 0; i--) { - // Iterate backwards as we're pushing items in the reverse order. - args.push_front(exec_args[i].replace(" ", "%20")); - } - - // Append Godot-specific custom arguments. - custom_args = _split_cmdline_args(raw_custom_args.substr(placeholder_pos + String("%command%").size())); - for (int i = 0; i < custom_args.size(); i++) { - args.push_back(custom_args[i].replace(" ", "%20")); - } - } else { - // Append Godot-specific custom arguments. - custom_args = _split_cmdline_args(raw_custom_args); - for (int i = 0; i < custom_args.size(); i++) { - args.push_back(custom_args[i].replace(" ", "%20")); - } - } - } - // Pass the debugger stop shortcut to the running instance(s). String shortcut; VariantWriter::write_to_string(ED_GET_SHORTCUT("editor/stop_running_project"), shortcut); OS::get_singleton()->set_environment("__GODOT_EDITOR_STOP_SHORTCUT__", shortcut); - if (OS::get_singleton()->is_stdout_verbose()) { - print_line(vformat("Running: %s", exec)); - for (const String &E : args) { - print_line(vformat(" %s", E)); - } - } + String exec = OS::get_singleton()->get_executable_path(); + int instance_count = RunInstancesDialog::get_singleton()->get_instance_count(); + for (int i = 0; i < instance_count; i++) { + List instance_args(args); + RunInstancesDialog::get_singleton()->get_argument_list_for_instance(i, instance_args); + + if (OS::get_singleton()->is_stdout_verbose()) { + print_line(vformat("Running: %s", exec)); + for (const String &E : instance_args) { + print_line(" %s", E); + } + } - int instances = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_instances", 1); - for (int i = 0; i < instances; i++) { OS::ProcessID pid = 0; - Error err = OS::get_singleton()->create_instance(args, &pid); + Error err = OS::get_singleton()->create_instance(instance_args, &pid); ERR_FAIL_COND_V(err, err); if (pid != 0) { pids.push_back(pid); diff --git a/editor/editor_run.h b/editor/editor_run.h index 01904489cc0..bd6770ae3db 100644 --- a/editor/editor_run.h +++ b/editor/editor_run.h @@ -47,8 +47,6 @@ private: Status status; String running_scene; - Vector _split_cmdline_args(const String &arg_string); - public: Status get_status() const; String get_running_scene() const; diff --git a/editor/plugins/debugger_editor_plugin.cpp b/editor/plugins/debugger_editor_plugin.cpp index b9bde65f941..8f0198c2c07 100644 --- a/editor/plugins/debugger_editor_plugin.cpp +++ b/editor/plugins/debugger_editor_plugin.cpp @@ -37,6 +37,7 @@ #include "editor/editor_node.h" #include "editor/editor_settings.h" #include "editor/plugins/script_editor_plugin.h" +#include "editor/run_instances_dialog.h" #include "editor/themes/editor_scale.h" #include "scene/gui/menu_button.h" @@ -93,22 +94,13 @@ DebuggerEditorPlugin::DebuggerEditorPlugin(PopupMenu *p_debug_menu) { debug_menu->set_item_tooltip(-1, TTR("When this option is enabled, the editor debug server will stay open and listen for new sessions started outside of the editor itself.")); - // Multi-instance, start/stop - instances_menu = memnew(PopupMenu); - instances_menu->set_name("RunInstances"); - instances_menu->set_hide_on_checkable_item_selection(false); - - debug_menu->add_child(instances_menu); + // Multi-instance, start/stop. debug_menu->add_separator(); - debug_menu->add_submenu_item(TTR("Run Multiple Instances"), "RunInstances"); - - for (int i = 1; i <= 4; i++) { - instances_menu->add_radio_check_item(vformat(TTRN("Run %d Instance", "Run %d Instances", i), i)); - instances_menu->set_item_metadata(i - 1, i); - } - instances_menu->set_item_checked(0, true); - instances_menu->connect("index_pressed", callable_mp(this, &DebuggerEditorPlugin::_select_run_count)); + debug_menu->add_item(TTR("Run Multiple Instances..."), RUN_MULTIPLE_INSTANCES); debug_menu->connect("id_pressed", callable_mp(this, &DebuggerEditorPlugin::_menu_option)); + + run_instances_dialog = memnew(RunInstancesDialog); + EditorNode::get_singleton()->get_gui_base()->add_child(run_instances_dialog); } DebuggerEditorPlugin::~DebuggerEditorPlugin() { @@ -116,14 +108,6 @@ DebuggerEditorPlugin::~DebuggerEditorPlugin() { memdelete(file_server); } -void DebuggerEditorPlugin::_select_run_count(int p_index) { - int len = instances_menu->get_item_count(); - for (int idx = 0; idx < len; idx++) { - instances_menu->set_item_checked(idx, idx == p_index); - } - EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_debug_instances", instances_menu->get_item_metadata(p_index)); -} - void DebuggerEditorPlugin::_menu_option(int p_option) { switch (p_option) { case RUN_FILE_SERVER: { @@ -201,6 +185,10 @@ void DebuggerEditorPlugin::_menu_option(int p_option) { EditorSettings::get_singleton()->set_project_metadata("debug_options", "server_keep_open", !ischecked); } break; + case RUN_MULTIPLE_INSTANCES: { + run_instances_dialog->popup_centered(); + + } break; } } @@ -227,7 +215,6 @@ void DebuggerEditorPlugin::_update_debug_options() { bool check_live_debug = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_live_debug", true); bool check_reload_scripts = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_reload_scripts", true); bool check_server_keep_open = EditorSettings::get_singleton()->get_project_metadata("debug_options", "server_keep_open", false); - int instances = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_instances", 1); if (check_deploy_remote) { _menu_option(RUN_DEPLOY_REMOTE_DEBUG); @@ -259,10 +246,4 @@ void DebuggerEditorPlugin::_update_debug_options() { if (check_server_keep_open) { _menu_option(SERVER_KEEP_OPEN); } - - int len = instances_menu->get_item_count(); - for (int idx = 0; idx < len; idx++) { - bool checked = (int)instances_menu->get_item_metadata(idx) == instances; - instances_menu->set_item_checked(idx, checked); - } } diff --git a/editor/plugins/debugger_editor_plugin.h b/editor/plugins/debugger_editor_plugin.h index 8d65dbd2e48..b7453e3e81e 100644 --- a/editor/plugins/debugger_editor_plugin.h +++ b/editor/plugins/debugger_editor_plugin.h @@ -36,6 +36,7 @@ class EditorFileServer; class MenuButton; class PopupMenu; +class RunInstancesDialog; class DebuggerEditorPlugin : public EditorPlugin { GDCLASS(DebuggerEditorPlugin, EditorPlugin); @@ -43,7 +44,7 @@ class DebuggerEditorPlugin : public EditorPlugin { private: PopupMenu *debug_menu = nullptr; EditorFileServer *file_server = nullptr; - PopupMenu *instances_menu = nullptr; + RunInstancesDialog *run_instances_dialog = nullptr; enum MenuOptions { RUN_FILE_SERVER, @@ -56,11 +57,11 @@ private: RUN_DEPLOY_REMOTE_DEBUG, RUN_RELOAD_SCRIPTS, SERVER_KEEP_OPEN, + RUN_MULTIPLE_INSTANCES, }; void _update_debug_options(); void _notification(int p_what); - void _select_run_count(int p_index); void _menu_option(int p_option); public: diff --git a/editor/run_instances_dialog.cpp b/editor/run_instances_dialog.cpp new file mode 100644 index 00000000000..0004b833f29 --- /dev/null +++ b/editor/run_instances_dialog.cpp @@ -0,0 +1,299 @@ +/**************************************************************************/ +/* run_instances_dialog.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "run_instances_dialog.h" + +#include "core/config/project_settings.h" +#include "editor/editor_settings.h" +#include "editor/themes/editor_scale.h" +#include "scene/gui/check_box.h" +#include "scene/gui/label.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/spin_box.h" +#include "scene/main/timer.h" + +RunInstancesDialog *RunInstancesDialog::singleton = nullptr; + +void RunInstancesDialog::_fetch_main_args() { + if (!main_args_edit->has_focus()) { // Only set the text if the user is not currently editing it. + main_args_edit->set_text(GLOBAL_GET("editor/run/main_run_args")); + } +} + +void RunInstancesDialog::_start_main_timer() { + main_apply_timer->start(); +} + +void RunInstancesDialog::_start_instance_timer() { + instance_apply_timer->start(); +} + +void RunInstancesDialog::_refresh_argument_count() { + while (argument_container->get_child_count() > 0) { + memdelete(argument_container->get_child(0)); + } + + override_list.resize(instance_count->get_value()); + argument_list.resize_zeroed(instance_count->get_value()); + + for (int i = 0; i < argument_list.size(); i++) { + VBoxContainer *instance_vb = memnew(VBoxContainer); + argument_container->add_child(instance_vb); + + HBoxContainer *hbox = memnew(HBoxContainer); + instance_vb->add_child(hbox); + + Label *l = memnew(Label); + hbox->add_child(l); + l->set_text(vformat(TTR("Instance %d"), i + 1)); + + CheckBox *cb = memnew(CheckBox); + hbox->add_child(cb); + cb->set_text(TTR("Override Main Run Args")); + cb->set_tooltip_text(TTR("If disabled, the instance arguments will be appended after the Main Run Args.")); + cb->set_pressed(override_list[i]); + cb->set_h_size_flags(Control::SIZE_SHRINK_END | Control::SIZE_EXPAND); + cb->connect(SNAME("toggled"), callable_mp(this, &RunInstancesDialog::_start_instance_timer).unbind(1)); + instance_vb->set_meta(SNAME("override"), cb); + + LineEdit *le = memnew(LineEdit); + instance_vb->add_child(le); + le->set_text(argument_list[i]); + le->connect(SNAME("text_changed"), callable_mp(this, &RunInstancesDialog::_start_instance_timer).unbind(1)); + instance_vb->set_meta(SNAME("args"), le); + } +} + +void RunInstancesDialog::_save_main_args() { + ProjectSettings::get_singleton()->set_setting("editor/run/main_run_args", main_args_edit->get_text()); + ProjectSettings::get_singleton()->save(); +} + +void RunInstancesDialog::_save_arguments() { + override_list.clear(); + override_list.resize(argument_container->get_child_count()); + argument_list.clear(); + argument_list.resize(argument_container->get_child_count()); + + String *w = argument_list.ptrw(); + for (int i = 0; i < argument_container->get_child_count(); i++) { + const Node *instance_vb = argument_container->get_child(i); + + CheckBox *check_box = Object::cast_to(instance_vb->get_meta(SNAME("override"))); + ERR_FAIL_NULL(check_box); + override_list[i] = check_box->is_pressed(); + + LineEdit *edit = Object::cast_to(instance_vb->get_meta(SNAME("args"))); + ERR_FAIL_NULL(edit); + w[i] = edit->get_text(); + } + + EditorSettings::get_singleton()->set_project_metadata("debug_options", "multiple_instances_enabled", enable_multiple_instances_checkbox->is_pressed()); + EditorSettings::get_singleton()->set_project_metadata("debug_options", "multiple_instances_overrides", override_list); + EditorSettings::get_singleton()->set_project_metadata("debug_options", "multiple_instances_arguments", argument_list); +} + +Vector RunInstancesDialog::_split_cmdline_args(const String &p_arg_string) const { + Vector split_args; + int arg_start = 0; + bool is_quoted = false; + char32_t quote_char = '-'; + char32_t arg_char; + int arg_length; + for (int i = 0; i < p_arg_string.length(); i++) { + arg_char = p_arg_string[i]; + if (arg_char == '\"' || arg_char == '\'') { + if (i == 0 || p_arg_string[i - 1] != '\\') { + if (is_quoted) { + if (arg_char == quote_char) { + is_quoted = false; + quote_char = '-'; + } + } else { + is_quoted = true; + quote_char = arg_char; + } + } + } else if (!is_quoted && arg_char == ' ') { + arg_length = i - arg_start; + if (arg_length > 0) { + split_args.push_back(p_arg_string.substr(arg_start, arg_length)); + } + arg_start = i + 1; + } + } + arg_length = p_arg_string.length() - arg_start; + if (arg_length > 0) { + split_args.push_back(p_arg_string.substr(arg_start, arg_length)); + } + return split_args; +} + +int RunInstancesDialog::get_instance_count() const { + if (enable_multiple_instances_checkbox->is_pressed()) { + return instance_count->get_value(); + } else { + return 1; + } +} + +void RunInstancesDialog::get_argument_list_for_instance(int p_idx, List &r_list) const { + bool override_args = override_list[p_idx]; + bool use_multiple_instances = enable_multiple_instances_checkbox->is_pressed(); + String raw_custom_args; + + if (use_multiple_instances && override_args) { + raw_custom_args = argument_list[p_idx]; + } else { + raw_custom_args = main_args_edit->get_text(); + } + + String exec = OS::get_singleton()->get_executable_path(); + + if (!raw_custom_args.is_empty()) { + // Allow the user to specify a command to run, similar to Steam's launch options. + // In this case, Godot will no longer be run directly; it's up to the underlying command + // to run it. For instance, this can be used on Linux to force a running project + // to use Optimus using `prime-run` or similar. + // Example: `prime-run %command% --time-scale 0.5` + const int placeholder_pos = raw_custom_args.find("%command%"); + + Vector custom_args; + + if (placeholder_pos != -1) { + // Prepend executable-specific custom arguments. + // If nothing is placed before `%command%`, behave as if no placeholder was specified. + Vector exec_args = _split_cmdline_args(raw_custom_args.substr(0, placeholder_pos)); + if (exec_args.size() > 0) { + exec = exec_args[0]; + exec_args.remove_at(0); + + // Append the Godot executable name before we append executable arguments + // (since the order is reversed when using `push_front()`). + r_list.push_front(OS::get_singleton()->get_executable_path()); + } + + for (int i = exec_args.size() - 1; i >= 0; i--) { + // Iterate backwards as we're pushing items in the reverse order. + r_list.push_front(exec_args[i].replace(" ", "%20")); + } + + // Append Godot-specific custom arguments. + custom_args = _split_cmdline_args(raw_custom_args.substr(placeholder_pos + String("%command%").size())); + for (int i = 0; i < custom_args.size(); i++) { + r_list.push_back(custom_args[i].replace(" ", "%20")); + } + } else { + // Append Godot-specific custom arguments. + custom_args = _split_cmdline_args(raw_custom_args); + for (int i = 0; i < custom_args.size(); i++) { + r_list.push_back(custom_args[i].replace(" ", "%20")); + } + } + } + + if (use_multiple_instances && !override_args) { + r_list.push_back(argument_list[p_idx]); + } +} + +RunInstancesDialog::RunInstancesDialog() { + singleton = this; + set_min_size(Vector2i(0, 600 * EDSCALE)); + + main_apply_timer = memnew(Timer); + main_apply_timer->set_wait_time(0.5); + main_apply_timer->set_one_shot(true); + add_child(main_apply_timer); + main_apply_timer->connect("timeout", callable_mp(this, &RunInstancesDialog::_save_main_args)); + + instance_apply_timer = memnew(Timer); + instance_apply_timer->set_wait_time(0.5); + instance_apply_timer->set_one_shot(true); + add_child(instance_apply_timer); + instance_apply_timer->connect("timeout", callable_mp(this, &RunInstancesDialog::_save_arguments)); + + VBoxContainer *main_vb = memnew(VBoxContainer); + add_child(main_vb); + + { + Label *l = memnew(Label); + main_vb->add_child(l); + l->set_text(TTR("Main Run Args:")); + } + + main_args_edit = memnew(LineEdit); + main_vb->add_child(main_args_edit); + _fetch_main_args(); + ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &RunInstancesDialog::_fetch_main_args)); + main_args_edit->connect("text_changed", callable_mp(this, &RunInstancesDialog::_start_main_timer).unbind(1)); + + enable_multiple_instances_checkbox = memnew(CheckBox); + enable_multiple_instances_checkbox->set_text(TTR("Enable Multiple Instances")); + enable_multiple_instances_checkbox->set_pressed(EditorSettings::get_singleton()->get_project_metadata("debug_options", "multiple_instances_enabled", false)); + main_vb->add_child(enable_multiple_instances_checkbox); + enable_multiple_instances_checkbox->connect("pressed", callable_mp(this, &RunInstancesDialog::_start_instance_timer)); + + override_list = EditorSettings::get_singleton()->get_project_metadata("debug_options", "multiple_instances_overrides", varray(false, false, false, false)); + argument_list = EditorSettings::get_singleton()->get_project_metadata("debug_options", "multiple_instances_arguments", PackedStringArray{ "", "", "", "" }); + + instance_count = memnew(SpinBox); + instance_count->set_min(1); + instance_count->set_max(20); + instance_count->set_value(argument_list.size()); + main_vb->add_child(instance_count); + instance_count->connect("value_changed", callable_mp(this, &RunInstancesDialog::_start_instance_timer).unbind(1)); + instance_count->connect("value_changed", callable_mp(this, &RunInstancesDialog::_refresh_argument_count).unbind(1)); + enable_multiple_instances_checkbox->connect("toggled", callable_mp(instance_count, &SpinBox::set_editable)); + + { + Label *l = memnew(Label); + l->set_text(TTR("Launch Arguments")); + l->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + main_vb->add_child(l); + } + + { + ScrollContainer *arguments_scroll = memnew(ScrollContainer); + arguments_scroll->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); + arguments_scroll->set_h_size_flags(Control::SIZE_EXPAND_FILL); + arguments_scroll->set_v_size_flags(Control::SIZE_EXPAND_FILL); + main_vb->add_child(arguments_scroll); + + argument_container = memnew(VBoxContainer); + argument_container->set_h_size_flags(Control::SIZE_EXPAND_FILL); + arguments_scroll->add_child(argument_container); + } + + _refresh_argument_count(); + + set_title(TTR("Run Multiple Instances")); + set_min_size(Size2i(400 * EDSCALE, 0)); +} diff --git a/editor/run_instances_dialog.h b/editor/run_instances_dialog.h new file mode 100644 index 00000000000..cc4cd6eb5bd --- /dev/null +++ b/editor/run_instances_dialog.h @@ -0,0 +1,78 @@ +/**************************************************************************/ +/* run_instances_dialog.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef RUN_INSTANCES_DIALOG_H +#define RUN_INSTANCES_DIALOG_H + +#include "scene/gui/dialogs.h" + +class CheckBox; +class LineEdit; +class SpinBox; +class Timer; +class VBoxContainer; + +class RunInstancesDialog : public AcceptDialog { + GDCLASS(RunInstancesDialog, AcceptDialog); + +private: + static RunInstancesDialog *singleton; + + Timer *main_apply_timer = nullptr; + Timer *instance_apply_timer = nullptr; + + LineEdit *main_args_edit = nullptr; + SpinBox *instance_count = nullptr; + CheckBox *enable_multiple_instances_checkbox = nullptr; + VBoxContainer *argument_container = nullptr; + + Array override_list; + PackedStringArray argument_list; + + void _fetch_main_args(); + // These 2 methods are necessary due to callable_mp() not supporting default arguments. + void _start_main_timer(); + void _start_instance_timer(); + + void _refresh_argument_count(); + void _save_main_args(); + void _save_arguments(); + // Separates command line arguments without splitting up quoted strings. + Vector _split_cmdline_args(const String &p_arg_string) const; + +public: + int get_instance_count() const; + void get_argument_list_for_instance(int p_idx, List &r_list) const; + + static RunInstancesDialog *get_singleton() { return singleton; } + RunInstancesDialog(); +}; + +#endif // RUN_INSTANCES_DIALOG_H