Path: blob/master/editor/scene/connections_dialog.cpp
20903 views
/**************************************************************************/1/* connections_dialog.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "connections_dialog.h"3132#include "core/config/project_settings.h"33#include "core/templates/hash_set.h"34#include "editor/doc/editor_help.h"35#include "editor/docks/scene_tree_dock.h"36#include "editor/docks/signals_dock.h"37#include "editor/editor_main_screen.h"38#include "editor/editor_node.h"39#include "editor/editor_string_names.h"40#include "editor/editor_undo_redo_manager.h"41#include "editor/gui/editor_variant_type_selectors.h"42#include "editor/inspector/editor_inspector.h"43#include "editor/scene/scene_tree_editor.h"44#include "editor/script/script_editor_plugin.h"45#include "editor/settings/editor_settings.h"46#include "editor/themes/editor_scale.h"47#include "scene/gui/button.h"48#include "scene/gui/check_box.h"49#include "scene/gui/check_button.h"50#include "scene/gui/flow_container.h"51#include "scene/gui/label.h"52#include "scene/gui/line_edit.h"53#include "scene/gui/margin_container.h"54#include "scene/gui/popup_menu.h"55#include "scene/gui/spin_box.h"5657static Node *_find_first_script(Node *p_root, Node *p_node) {58if (p_node != p_root && p_node->get_owner() != p_root) {59return nullptr;60}61if (!p_node->get_script().is_null()) {62return p_node;63}6465for (int i = 0; i < p_node->get_child_count(); i++) {66Node *ret = _find_first_script(p_root, p_node->get_child(i));67if (ret) {68return ret;69}70}7172return nullptr;73}7475class ConnectDialogBinds : public Object {76GDCLASS(ConnectDialogBinds, Object);7778public:79Vector<Variant> params;8081bool _set(const StringName &p_name, const Variant &p_value) {82String name = p_name;8384if (name.begins_with("bind/argument_")) {85int which = name.get_slicec('_', 1).to_int() - 1;86ERR_FAIL_INDEX_V(which, params.size(), false);87params.write[which] = p_value;88} else {89return false;90}9192return true;93}9495bool _get(const StringName &p_name, Variant &r_ret) const {96String name = p_name;9798if (name.begins_with("bind/argument_")) {99int which = name.get_slicec('_', 1).to_int() - 1;100ERR_FAIL_INDEX_V(which, params.size(), false);101r_ret = params[which];102} else {103return false;104}105106return true;107}108109void _get_property_list(List<PropertyInfo> *p_list) const {110for (int i = 0; i < params.size(); i++) {111p_list->push_back(PropertyInfo(params[i].get_type(), "bind/argument_" + itos(i + 1)));112}113}114115void update_base_node_relative(Node *p_node) {116Node *old_base = nullptr;117if (has_meta("__base_node_relative")) {118old_base = Object::cast_to<Node>(get_meta("__base_node_relative"));119}120121if (old_base == p_node) {122return;123}124// The cdbinds is a proxy object, so we want the node path to be relative to the target node.125set_meta("__base_node_relative", p_node);126127if (!old_base) {128return;129}130131// Update existing outdated node paths.132for (int i = 0; i < params.size(); i++) {133if (params[i].get_type() != Variant::NODE_PATH) {134continue;135}136StringName property_name = "bind/argument_" + itos(i + 1);137Node *n = old_base->get_node(get(property_name));138set(property_name, p_node ? p_node->get_path_to(n) : NodePath());139}140}141142void notify_changed() {143notify_property_list_changed();144}145146ConnectDialogBinds() {147}148};149150/*151* Signal automatically called by parent dialog.152*/153void ConnectDialog::ok_pressed() {154String method_name = dst_method->get_text();155156if (method_name.is_empty()) {157error->set_text(TTR("Method in target node must be specified."));158error->popup_centered();159return;160}161162if (!TS->is_valid_identifier(method_name.strip_edges())) {163error->set_text(TTR("Method name must be a valid identifier."));164error->popup_centered();165return;166}167168Node *target = tree->get_selected();169if (!target) {170return; // Nothing selected in the tree, not an error.171}172if (target->get_script().is_null()) {173if (!target->has_method(method_name)) {174error->set_text(TTR("Target method not found. Specify a valid method or attach a script to the target node."));175error->popup_centered();176return;177}178}179emit_signal(SNAME("connected"));180hide();181}182183void ConnectDialog::_cancel_pressed() {184hide();185}186187void ConnectDialog::_item_activated() {188_ok_pressed(); // From AcceptDialog.189}190191/*192* Called each time a target node is selected within the target node tree.193*/194void ConnectDialog::_tree_node_selected() {195Node *current = tree->get_selected();196197if (!current) {198return;199}200201Node *source_node = Object::cast_to<Node>(source);202if (source_node) {203dst_path = source_node->get_path_to(current);204}205206if (!edit_mode) {207set_dst_method(generate_method_callback_name(source, signal, current));208}209210cdbinds->update_base_node_relative(current);211212_update_method_tree();213_update_warning_label();214_update_ok_enabled();215}216217void ConnectDialog::_focus_currently_connected() {218tree->set_selected(Object::cast_to<Node>(source));219}220221void ConnectDialog::_method_selected() {222TreeItem *selected_item = method_tree->get_selected();223dst_method->set_text(selected_item->get_metadata(0));224}225226/*227* Adds a new parameter bind to connection.228*/229void ConnectDialog::_add_bind() {230Variant::Type type = type_list->get_selected_type();231232Variant value;233Callable::CallError err;234Variant::construct(type, value, nullptr, 0, err);235236cdbinds->params.push_back(value);237cdbinds->notify_changed();238}239240/*241* Remove parameter bind from connection.242*/243void ConnectDialog::_remove_bind() {244String st = bind_editor->get_selected_path();245if (st.is_empty()) {246return;247}248int idx = st.get_slicec('/', 1).to_int() - 1;249250ERR_FAIL_INDEX(idx, cdbinds->params.size());251cdbinds->params.remove_at(idx);252cdbinds->notify_changed();253}254/*255* Automatically generates a name for the callback method.256*/257StringName ConnectDialog::generate_method_callback_name(Object *p_source, const String &p_signal_name, Object *p_target) {258String node_name = p_source->call("get_name");259260for (int i = 0; i < node_name.length(); i++) { // TODO: Regex filter may be cleaner.261char32_t c = node_name[i];262if ((i == 0 && !is_unicode_identifier_start(c)) || (i > 0 && !is_unicode_identifier_continue(c))) {263if (c == ' ') {264// Replace spaces with underlines.265c = '_';266} else {267// Remove any other characters.268node_name.remove_at(i);269i--;270continue;271}272}273node_name[i] = c;274}275276Dictionary subst;277subst["NodeName"] = node_name.to_pascal_case();278subst["nodeName"] = node_name.to_camel_case();279subst["node_name"] = node_name.to_snake_case();280subst["node-name"] = node_name.to_kebab_case();281282subst["SignalName"] = p_signal_name.to_pascal_case();283subst["signalName"] = p_signal_name.to_camel_case();284subst["signal_name"] = p_signal_name.to_snake_case();285subst["signal-name"] = p_signal_name.to_kebab_case();286287String dst_method;288if (p_source == p_target) {289dst_method = String(GLOBAL_GET("editor/naming/default_signal_callback_to_self_name")).format(subst);290} else {291dst_method = String(GLOBAL_GET("editor/naming/default_signal_callback_name")).format(subst);292}293294return dst_method;295}296297void ConnectDialog::_create_method_tree_items(const List<MethodInfo> &p_methods, TreeItem *p_parent_item) {298for (const MethodInfo &mi : p_methods) {299TreeItem *method_item = method_tree->create_item(p_parent_item);300method_item->set_text(0, get_signature(mi));301method_item->set_metadata(0, mi.name);302}303}304305List<MethodInfo> ConnectDialog::_filter_method_list(const List<MethodInfo> &p_methods, const MethodInfo &p_signal, const String &p_search_string) const {306bool check_signal = compatible_methods_only->is_pressed();307List<MethodInfo> ret;308309LocalVector<Pair<Variant::Type, StringName>> effective_args;310int unbind = get_unbinds();311effective_args.reserve(MAX(p_signal.arguments.size() - unbind, 0));312for (int64_t i = 0; i < p_signal.arguments.size() - unbind; i++) {313PropertyInfo pi = p_signal.arguments[i];314effective_args.push_back(Pair(pi.type, pi.class_name));315}316317for (const Variant &variant : get_binds()) {318effective_args.push_back(Pair(variant.get_type(), StringName()));319}320321for (const MethodInfo &mi : p_methods) {322if (mi.name.begins_with("@")) {323// GH-92782. GDScript inline setters/getters are historically present in `get_method_list()`324// and can be called using `Object.call()`. However, these functions are meant to be internal325// and their names are not valid identifiers, so let's hide them from the user.326continue;327}328329if (!p_search_string.is_empty() && !mi.name.containsn(p_search_string)) {330continue;331}332333if (check_signal) {334const unsigned min_argc = mi.arguments.size() - mi.default_arguments.size();335const unsigned max_argc = (mi.flags & METHOD_FLAG_VARARG) ? UINT_MAX : mi.arguments.size();336337if (effective_args.size() < min_argc || effective_args.size() > max_argc) {338continue;339}340341bool type_mismatch = false;342for (int64_t i = 0; i < effective_args.size() && i < mi.arguments.size(); ++i) {343Variant::Type stype = effective_args[i].first;344Variant::Type mtype = mi.arguments[i].type;345346if (stype != Variant::NIL && mtype != Variant::NIL && stype != mtype) {347type_mismatch = true;348break;349}350351if (stype == Variant::OBJECT && mtype == Variant::OBJECT && !ClassDB::is_parent_class(effective_args[i].second, mi.arguments[i].class_name)) {352type_mismatch = true;353break;354}355}356357if (type_mismatch) {358continue;359}360}361362ret.push_back(mi);363}364365return ret;366}367368void ConnectDialog::_update_method_tree() {369method_tree->clear();370371Color disabled_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor)) * 0.7;372String search_string = method_search->get_text();373Node *target = tree->get_selected();374if (!target) {375return;376}377378MethodInfo signal_info;379if (compatible_methods_only->is_pressed()) {380List<MethodInfo> signals;381source->get_signal_list(&signals);382for (const MethodInfo &mi : signals) {383if (mi.name == signal) {384signal_info = mi;385break;386}387}388}389390TreeItem *root_item = method_tree->create_item();391root_item->set_text(0, TTR("Methods"));392root_item->set_selectable(0, false);393394// If a script is attached, get methods from it.395ScriptInstance *si = target->get_script_instance();396if (si) {397if (si->get_script()->is_built_in()) {398si->get_script()->reload();399}400List<MethodInfo> methods;401si->get_method_list(&methods);402methods = _filter_method_list(methods, signal_info, search_string);403404if (!methods.is_empty()) {405TreeItem *si_item = method_tree->create_item(root_item);406si_item->set_text(0, TTR("Attached Script"));407si_item->set_icon(0, get_editor_theme_icon(SNAME("Script")));408si_item->set_selectable(0, false);409410_create_method_tree_items(methods, si_item);411}412}413414if (script_methods_only->is_pressed()) {415empty_tree_label->set_visible(root_item->get_first_child() == nullptr);416return;417}418419// Get methods from each class in the hierarchy.420StringName current_class = target->get_class_name();421do {422TreeItem *class_item = method_tree->create_item(root_item);423class_item->set_text(0, current_class);424Ref<Texture2D> icon = get_editor_theme_icon(SNAME("Node"));425if (has_theme_icon(current_class, EditorStringName(EditorIcons))) {426icon = get_editor_theme_icon(current_class);427}428class_item->set_icon(0, icon);429class_item->set_selectable(0, false);430431List<MethodInfo> methods;432ClassDB::get_method_list(current_class, &methods, true);433methods = _filter_method_list(methods, signal_info, search_string);434435if (methods.is_empty()) {436class_item->set_custom_color(0, disabled_color);437} else {438_create_method_tree_items(methods, class_item);439}440current_class = ClassDB::get_parent_class_nocheck(current_class);441} while (current_class != StringName());442443empty_tree_label->set_visible(root_item->get_first_child() == nullptr);444}445446void ConnectDialog::_method_check_button_pressed(const CheckButton *p_button) {447if (p_button == script_methods_only) {448EditorSettings::get_singleton()->set_project_metadata("editor_metadata", "show_script_methods_only", p_button->is_pressed());449} else if (p_button == compatible_methods_only) {450EditorSettings::get_singleton()->set_project_metadata("editor_metadata", "show_compatible_methods_only", p_button->is_pressed());451}452_update_method_tree();453}454455void ConnectDialog::_open_method_popup() {456method_popup->popup_centered();457method_search->clear();458method_search->grab_focus();459}460461/*462* Enables or disables the connect button. The connect button is enabled if a463* node is selected and valid in the selected mode.464*/465void ConnectDialog::_update_ok_enabled() {466Node *target = tree->get_selected();467468if (target == nullptr) {469get_ok_button()->set_disabled(true);470return;471}472473if (dst_method->get_text().is_empty()) {474get_ok_button()->set_disabled(true);475return;476}477478get_ok_button()->set_disabled(false);479}480481void ConnectDialog::_update_warning_label() {482Node *dst = Object::cast_to<Node>(source)->get_node(dst_path);483484if (dst == nullptr) {485warning_label->set_visible(false);486return;487}488489Ref<Script> scr = dst->get_script();490if (scr.is_null()) {491warning_label->set_visible(false);492return;493}494495ScriptLanguage *language = scr->get_language();496if (language->can_make_function()) {497warning_label->set_visible(false);498return;499}500501warning_label->set_text(vformat(TTR("%s: Callback code won't be generated, please add it manually."), language->get_name()));502warning_label->set_visible(true);503}504505void ConnectDialog::_post_popup() {506callable_mp((Control *)dst_method, &Control::grab_focus).call_deferred(false);507callable_mp(dst_method, &LineEdit::select_all).call_deferred();508}509510void ConnectDialog::_notification(int p_what) {511switch (p_what) {512case NOTIFICATION_ENTER_TREE: {513bind_editor->edit(cdbinds);514515[[fallthrough]];516}517case NOTIFICATION_THEME_CHANGED: {518method_search->set_right_icon(get_editor_theme_icon("Search"));519open_method_tree->set_button_icon(get_editor_theme_icon("Edit"));520} break;521}522}523524void ConnectDialog::_bind_methods() {525ADD_SIGNAL(MethodInfo("connected"));526}527528Object *ConnectDialog::get_source() const {529return source;530}531532ConnectDialog::ConnectionData ConnectDialog::get_source_connection_data() const {533return source_connection_data;534}535536StringName ConnectDialog::get_signal_name() const {537return signal;538}539540PackedStringArray ConnectDialog::get_signal_args() const {541return signal_args;542}543544NodePath ConnectDialog::get_dst_path() const {545return dst_path;546}547548void ConnectDialog::set_dst_node(Node *p_node) {549tree->set_selected(p_node);550}551552StringName ConnectDialog::get_dst_method_name() const {553String txt = dst_method->get_text();554if (txt.contains_char('(')) {555txt = txt.left(txt.find_char('(')).strip_edges();556}557return txt;558}559560void ConnectDialog::set_dst_method(const StringName &p_method) {561dst_method->set_text(p_method);562}563564int ConnectDialog::get_unbinds() const {565return int(unbind_count->get_value());566}567568Vector<Variant> ConnectDialog::get_binds() const {569return cdbinds->params;570}571572String ConnectDialog::get_signature(const MethodInfo &p_method, PackedStringArray *r_arg_names) {573PackedStringArray signature;574signature.append(p_method.name);575signature.append("(");576577for (int64_t i = 0; i < p_method.arguments.size(); ++i) {578if (i > 0) {579signature.append(", ");580}581582const PropertyInfo &pi = p_method.arguments[i];583String type_name;584switch (pi.type) {585case Variant::NIL:586type_name = "Variant";587break;588case Variant::INT:589if ((pi.usage & PROPERTY_USAGE_CLASS_IS_ENUM) && pi.class_name != StringName() && !String(pi.class_name).begins_with("res://")) {590type_name = pi.class_name;591} else {592type_name = "int";593}594break;595case Variant::ARRAY:596if (pi.hint == PROPERTY_HINT_ARRAY_TYPE && !pi.hint_string.is_empty() && !pi.hint_string.begins_with("res://")) {597type_name = "Array[" + pi.hint_string + "]";598} else {599type_name = "Array";600}601break;602case Variant::DICTIONARY:603type_name = "Dictionary";604if (pi.hint == PROPERTY_HINT_DICTIONARY_TYPE && !pi.hint_string.is_empty()) {605String key_hint = pi.hint_string.get_slicec(';', 0);606String value_hint = pi.hint_string.get_slicec(';', 1);607if (key_hint.is_empty() || key_hint.begins_with("res://")) {608key_hint = "Variant";609}610if (value_hint.is_empty() || value_hint.begins_with("res://")) {611value_hint = "Variant";612}613if (key_hint != "Variant" || value_hint != "Variant") {614type_name += "[" + key_hint + ", " + value_hint + "]";615}616}617break;618case Variant::OBJECT:619if (pi.class_name != StringName()) {620type_name = pi.class_name;621} else {622type_name = "Object";623}624break;625default:626type_name = Variant::get_type_name(pi.type);627break;628}629630String arg_name = pi.name.is_empty() ? "arg" + itos(i) : pi.name;631signature.append(arg_name + ": " + type_name);632if (r_arg_names) {633r_arg_names->push_back(arg_name + ": " + type_name);634}635}636637if (p_method.flags & METHOD_FLAG_VARARG) {638signature.append(p_method.arguments.is_empty() ? "..." : ", ...");639}640641signature.append(")");642return String().join(signature);643}644645bool ConnectDialog::get_deferred() const {646return deferred->is_pressed();647}648649bool ConnectDialog::get_one_shot() const {650return one_shot->is_pressed();651}652653bool ConnectDialog::get_append_source() const {654return !append_source->is_disabled() && append_source->is_pressed();655}656657/*658* Returns true if ConnectDialog is being used to edit an existing connection.659*/660bool ConnectDialog::is_editing() const {661return edit_mode;662}663664void ConnectDialog::shortcut_input(const Ref<InputEvent> &p_event) {665const Ref<InputEventKey> &key = p_event;666667if (key.is_valid() && key->is_pressed() && !key->is_echo()) {668if (ED_IS_SHORTCUT("editor/open_search", p_event)) {669filter_nodes->grab_focus();670filter_nodes->select_all();671filter_nodes->accept_event();672}673}674}675676/*677* Initialize ConnectDialog and populate fields with expected data.678* If creating a connection from scratch, sensible defaults are used.679* If editing an existing connection, previous data is retained.680*/681void ConnectDialog::init(const ConnectionData &p_cd, const PackedStringArray &p_signal_args, bool p_edit) {682set_hide_on_ok(false);683684source = p_cd.source;685signal = p_cd.signal;686signal_args = p_signal_args;687688tree->set_selected(nullptr);689tree->set_marked(Object::cast_to<Node>(source));690691if (p_cd.target) {692set_dst_node(Object::cast_to<Node>(p_cd.target));693set_dst_method(p_cd.method);694}695696_update_ok_enabled();697698bool b_deferred = (p_cd.flags & CONNECT_DEFERRED);699bool b_oneshot = (p_cd.flags & CONNECT_ONE_SHOT);700bool b_append_source = (p_cd.flags & CONNECT_APPEND_SOURCE_OBJECT);701702deferred->set_pressed(b_deferred);703one_shot->set_pressed(b_oneshot);704append_source->set_pressed(b_append_source);705706unbind_count->set_max(p_signal_args.size());707unbind_count->set_value(p_cd.unbinds);708709cdbinds->params.clear();710cdbinds->params = p_cd.binds;711cdbinds->notify_changed();712713edit_mode = p_edit;714715source_connection_data = p_cd;716}717718void ConnectDialog::popup_dialog(const String &p_for_signal) {719from_signal->set_text(p_for_signal);720warning_label->add_theme_color_override(SceneStringName(font_color), warning_label->get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));721error_label->add_theme_color_override(SceneStringName(font_color), error_label->get_theme_color(SNAME("error_color"), EditorStringName(Editor)));722filter_nodes->clear();723724if (!advanced->is_pressed()) {725error_label->set_visible(!_find_first_script(get_tree()->get_edited_scene_root(), get_tree()->get_edited_scene_root()));726}727728if (first_popup) {729first_popup = false;730_advanced_pressed();731}732733popup_centered();734}735736void ConnectDialog::_advanced_pressed() {737if (advanced->is_pressed()) {738connect_to_label->set_text(TTR("Connect to Node:"));739tree->set_connect_to_script_mode(false);740741vbc_right->show();742error_label->hide();743} else {744reset_size();745connect_to_label->set_text(TTR("Connect to Script:"));746tree->set_connect_to_script_mode(true);747748vbc_right->hide();749error_label->set_visible(!_find_first_script(get_tree()->get_edited_scene_root(), get_tree()->get_edited_scene_root()));750}751752EditorSettings::get_singleton()->set_project_metadata("editor_metadata", "use_advanced_connections", advanced->is_pressed());753754popup_centered();755}756757ConnectDialog::ConnectDialog() {758set_min_size(Size2(0, 500) * EDSCALE);759760HBoxContainer *main_hb = memnew(HBoxContainer);761add_child(main_hb);762763VBoxContainer *vbc_left = memnew(VBoxContainer);764main_hb->add_child(vbc_left);765vbc_left->set_h_size_flags(Control::SIZE_EXPAND_FILL);766vbc_left->set_custom_minimum_size(Vector2(400 * EDSCALE, 0));767768from_signal = memnew(LineEdit);769from_signal->set_accessibility_name(TTRC("From Signal:"));770vbc_left->add_margin_child(TTR("From Signal:"), from_signal);771from_signal->set_editable(false);772773tree = memnew(SceneTreeEditor(false));774tree->set_update_when_invisible(false);775tree->set_connecting_signal(true);776tree->set_show_enabled_subscene(true);777tree->set_v_size_flags(Control::SIZE_FILL | Control::SIZE_EXPAND);778tree->get_scene_tree()->connect("item_activated", callable_mp(this, &ConnectDialog::_item_activated));779tree->connect("node_selected", callable_mp(this, &ConnectDialog::_tree_node_selected));780tree->set_connect_to_script_mode(true);781tree->get_scene_tree()->set_theme_type_variation("TreeSecondary");782783HBoxContainer *hbc_filter = memnew(HBoxContainer);784785filter_nodes = memnew(LineEdit);786hbc_filter->add_child(filter_nodes);787filter_nodes->set_h_size_flags(Control::SIZE_FILL | Control::SIZE_EXPAND);788filter_nodes->set_placeholder(TTR("Filter Nodes"));789filter_nodes->set_accessibility_name(TTRC("Filter Nodes"));790filter_nodes->set_clear_button_enabled(true);791filter_nodes->connect(SceneStringName(text_changed), callable_mp(tree, &SceneTreeEditor::set_filter));792793Button *focus_current = memnew(Button);794hbc_filter->add_child(focus_current);795focus_current->set_text(TTR("Go to Source"));796focus_current->connect(SceneStringName(pressed), callable_mp(this, &ConnectDialog::_focus_currently_connected));797798Node *mc = vbc_left->add_margin_child(TTR("Connect to Script:"), hbc_filter, false);799connect_to_label = Object::cast_to<Label>(vbc_left->get_child(mc->get_index() - 1));800vbc_left->add_child(tree);801802warning_label = memnew(Label);803warning_label->set_focus_mode(Control::FOCUS_ACCESSIBILITY);804vbc_left->add_child(warning_label);805warning_label->hide();806807error_label = memnew(Label);808error_label->set_focus_mode(Control::FOCUS_ACCESSIBILITY);809error_label->set_text(TTR("Scene does not contain any script."));810vbc_left->add_child(error_label);811error_label->hide();812813method_popup = memnew(AcceptDialog);814method_popup->set_title(TTR("Select Method"));815method_popup->set_min_size(Vector2(400, 600) * EDSCALE);816add_child(method_popup);817818VBoxContainer *method_vbc = memnew(VBoxContainer);819method_popup->add_child(method_vbc);820821method_search = memnew(LineEdit);822method_vbc->add_child(method_search);823method_search->set_placeholder(TTR("Filter Methods"));824method_search->set_accessibility_name(TTRC("Filter Methods"));825method_search->set_clear_button_enabled(true);826method_search->connect(SceneStringName(text_changed), callable_mp(this, &ConnectDialog::_update_method_tree).unbind(1));827828method_tree = memnew(Tree);829method_vbc->add_child(method_tree);830method_tree->set_accessibility_name(TTRC("Methods"));831method_tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);832method_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);833method_tree->set_hide_root(true);834method_tree->connect(SceneStringName(item_selected), callable_mp(this, &ConnectDialog::_method_selected));835method_tree->connect("item_activated", callable_mp((Window *)method_popup, &Window::hide));836837empty_tree_label = memnew(Label(TTR("No method found matching given filters.")));838method_popup->add_child(empty_tree_label);839empty_tree_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);840empty_tree_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);841empty_tree_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD);842843script_methods_only = memnew(CheckButton(TTR("Script Methods Only")));844method_vbc->add_child(script_methods_only);845script_methods_only->set_h_size_flags(Control::SIZE_SHRINK_END);846script_methods_only->set_pressed(EditorSettings::get_singleton()->get_project_metadata("editor_metadata", "show_script_methods_only", true));847script_methods_only->connect(SceneStringName(pressed), callable_mp(this, &ConnectDialog::_method_check_button_pressed).bind(script_methods_only));848849compatible_methods_only = memnew(CheckButton(TTR("Compatible Methods Only")));850method_vbc->add_child(compatible_methods_only);851compatible_methods_only->set_h_size_flags(Control::SIZE_SHRINK_END);852compatible_methods_only->set_pressed(EditorSettings::get_singleton()->get_project_metadata("editor_metadata", "show_compatible_methods_only", true));853compatible_methods_only->connect(SceneStringName(pressed), callable_mp(this, &ConnectDialog::_method_check_button_pressed).bind(compatible_methods_only));854855vbc_right = memnew(VBoxContainer);856main_hb->add_child(vbc_right);857vbc_right->set_h_size_flags(Control::SIZE_EXPAND_FILL);858vbc_right->set_custom_minimum_size(Vector2(150 * EDSCALE, 0));859vbc_right->hide();860861HBoxContainer *add_bind_hb = memnew(HBoxContainer);862863type_list = memnew(EditorVariantTypeOptionButton);864type_list->set_accessibility_name(TTRC("Type"));865type_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);866type_list->populate({ Variant::NIL, Variant::OBJECT });867add_bind_hb->add_child(type_list);868bind_controls.push_back(type_list);869870Button *add_bind = memnew(Button);871add_bind->set_text(TTR("Add"));872add_bind_hb->add_child(add_bind);873add_bind->connect(SceneStringName(pressed), callable_mp(this, &ConnectDialog::_add_bind));874bind_controls.push_back(add_bind);875876Button *del_bind = memnew(Button);877del_bind->set_text(TTR("Remove"));878add_bind_hb->add_child(del_bind);879del_bind->connect(SceneStringName(pressed), callable_mp(this, &ConnectDialog::_remove_bind));880bind_controls.push_back(del_bind);881882vbc_right->add_margin_child(TTR("Add Extra Call Argument:"), add_bind_hb);883884bind_editor = memnew(EditorInspector);885bind_editor->set_accessibility_name(TTRC("Extra Call Arguments:"));886bind_editor->set_theme_type_variation("ScrollContainerSecondary");887bind_controls.push_back(bind_editor);888889vbc_right->add_margin_child(TTR("Extra Call Arguments:"), bind_editor, true);890891unbind_count = memnew(SpinBox);892unbind_count->set_tooltip_text(TTR("Allows to drop arguments sent by signal emitter."));893unbind_count->set_accessibility_name(TTRC("Unbind Signal Arguments:"));894895vbc_right->add_margin_child(TTR("Unbind Signal Arguments:"), unbind_count);896897HBoxContainer *hbc_method = memnew(HBoxContainer);898vbc_left->add_margin_child(TTR("Receiver Method:"), hbc_method);899900dst_method = memnew(LineEdit);901dst_method->set_accessibility_name(TTRC("Receiver Method"));902dst_method->set_h_size_flags(Control::SIZE_EXPAND_FILL);903dst_method->connect(SceneStringName(text_changed), callable_mp(method_tree, &Tree::deselect_all).unbind(1));904hbc_method->add_child(dst_method);905register_text_enter(dst_method);906907open_method_tree = memnew(Button(TTRC("Pick")));908hbc_method->add_child(open_method_tree);909open_method_tree->connect(SceneStringName(pressed), callable_mp(this, &ConnectDialog::_open_method_popup));910911advanced = memnew(CheckButton(TTR("Advanced")));912vbc_left->add_child(advanced);913advanced->set_h_size_flags(Control::SIZE_SHRINK_BEGIN | Control::SIZE_EXPAND);914advanced->set_pressed(EditorSettings::get_singleton()->get_project_metadata("editor_metadata", "use_advanced_connections", false));915advanced->connect(SceneStringName(pressed), callable_mp(this, &ConnectDialog::_advanced_pressed));916917FlowContainer *fc_flags = memnew(FlowContainer);918vbc_right->add_child(fc_flags);919920deferred = memnew(CheckBox);921deferred->set_text(TTR("Deferred"));922deferred->set_tooltip_text(TTR("Defers the signal, storing it in a queue and only firing it at idle time."));923fc_flags->add_child(deferred);924925one_shot = memnew(CheckBox);926one_shot->set_text(TTR("One Shot"));927one_shot->set_tooltip_text(TTR("Disconnects the signal after its first emission."));928fc_flags->add_child(one_shot);929930append_source = memnew(CheckBox);931append_source->set_text(TTRC("Append Source"));932append_source->set_tooltip_text(TTRC("The source object is automatically sent when the signal is emitted."));933fc_flags->add_child(append_source);934935cdbinds = memnew(ConnectDialogBinds);936937error = memnew(AcceptDialog);938add_child(error);939error->set_title(TTR("Cannot connect signal"));940error->set_ok_button_text(TTR("Close"));941set_ok_button_text(TTR("Connect"));942}943944ConnectDialog::~ConnectDialog() {945memdelete(cdbinds);946}947948//////////////////////////////////////////949950Control *ConnectionsDockTree::make_custom_tooltip(const String &p_text) const {951// If it's not a doc tooltip, fallback to the default one.952if (p_text.is_empty() || p_text.contains(" :: ")) {953return nullptr;954}955956return EditorHelpBitTooltip::make_tooltip(const_cast<ConnectionsDockTree *>(this), p_text);957}958959struct _ConnectionsDockMethodInfoSort {960_FORCE_INLINE_ bool operator()(const MethodInfo &a, const MethodInfo &b) const {961return a.name < b.name;962}963};964965void ConnectionsDock::_filter_changed(const String &p_text) {966update_tree();967}968969/*970* Post-ConnectDialog callback for creating/editing connections.971* Creates or edits connections based on state of the ConnectDialog when "Connect" is pressed.972*/973void ConnectionsDock::_make_or_edit_connection() {974NodePath dst_path = connect_dialog->get_dst_path();975Node *target = Object::cast_to<Node>(selected_object)->get_node(dst_path);976977ERR_FAIL_NULL(target);978979ConnectDialog::ConnectionData cd;980cd.source = connect_dialog->get_source();981cd.target = target;982cd.signal = connect_dialog->get_signal_name();983cd.method = connect_dialog->get_dst_method_name();984cd.unbinds = connect_dialog->get_unbinds();985cd.binds = connect_dialog->get_binds();986987bool b_deferred = connect_dialog->get_deferred();988bool b_oneshot = connect_dialog->get_one_shot();989bool b_append_source = connect_dialog->get_append_source();990cd.flags = CONNECT_PERSIST | (b_deferred ? CONNECT_DEFERRED : 0) | (b_oneshot ? CONNECT_ONE_SHOT : 0) | (b_append_source ? CONNECT_APPEND_SOURCE_OBJECT : 0);991992// If the function is found in target's own script, check the editor setting993// to determine if the script should be opened.994// If the function is found in an inherited class or script no need to do anything995// except making a connection.996bool add_script_function_request = false;997Ref<Script> scr = target->get_script();998999if (scr.is_valid() && !ClassDB::has_method(target->get_class(), cd.method)) {1000// Check in target's own script.1001int line = scr->get_language()->find_function(cd.method, scr->get_source_code());1002if (line != -1) {1003add_script_function_request = EDITOR_GET("text_editor/behavior/navigation/open_script_when_connecting_signal_to_existing_method");1004} else {1005// There is a chance that the method is inherited from another script.1006bool found_inherited_function = false;1007Ref<Script> inherited_scr = scr->get_base_script();1008while (inherited_scr.is_valid()) {1009int inherited_line = inherited_scr->get_language()->find_function(cd.method, inherited_scr->get_source_code());1010if (inherited_line != -1) {1011found_inherited_function = true;1012break;1013}10141015inherited_scr = inherited_scr->get_base_script();1016}10171018add_script_function_request = !found_inherited_function;1019}1020}10211022if (add_script_function_request) {1023PackedStringArray script_function_args = connect_dialog->get_signal_args();1024script_function_args.resize(script_function_args.size() - cd.unbinds);10251026// Append the source.1027if (b_append_source) {1028String class_name = cd.source->get_class();1029bool found = false;10301031Ref<Script> source_script = cd.source->get_script();1032if (source_script.is_valid()) {1033found = source_script->has_script_signal(cd.signal);1034if (found) {1035// Check global name in script inheritance chain.1036bool need_check = found;1037Ref<Script> base_script = source_script->get_base_script();1038while (base_script.is_valid()) {1039need_check = base_script->has_script_signal(cd.signal);1040if (!need_check) {1041break;1042}1043source_script = base_script;1044base_script = source_script->get_base_script();1045}1046class_name = source_script->get_global_name();1047}1048}10491050if (!found) {1051while (!class_name.is_empty()) {1052// Search in ClassDB according to the inheritance chain.1053found = ClassDB::has_signal(class_name, cd.signal, true);1054if (found) {1055break;1056}1057class_name = ClassDB::get_parent_class(class_name);1058}1059}10601061script_function_args.push_back("source:" + class_name);1062}10631064for (int i = 0; i < cd.binds.size(); i++) {1065script_function_args.push_back("extra_arg_" + itos(i) + ": " + Variant::get_type_name(cd.binds[i].get_type()));1066}10671068EditorNode::get_singleton()->emit_signal(SNAME("script_add_function_request"), cd.target, cd.method, script_function_args);1069}10701071if (connect_dialog->is_editing()) {1072_disconnect(connect_dialog->get_source_connection_data());1073_connect(cd);1074} else {1075_connect(cd);1076}10771078update_tree();1079}10801081/*1082* Creates single connection w/ undo-redo functionality.1083*/1084void ConnectionsDock::_connect(const ConnectDialog::ConnectionData &p_cd) {1085Object *source = p_cd.source;1086Node *target = Object::cast_to<Node>(p_cd.target);10871088if (!source || !target) {1089return;1090}10911092Callable callable = p_cd.get_callable();1093EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1094undo_redo->create_action(vformat(TTR("Connect '%s' to '%s'"), String(p_cd.signal), String(p_cd.method)));1095undo_redo->add_do_method(source, "connect", p_cd.signal, callable, p_cd.flags);1096undo_redo->add_undo_method(source, "disconnect", p_cd.signal, callable);1097undo_redo->add_do_method(this, "update_tree");1098undo_redo->add_undo_method(this, "update_tree");1099undo_redo->add_do_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree"); // To force redraw of scene tree.1100undo_redo->add_undo_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");11011102undo_redo->commit_action();1103}11041105/*1106* Break single connection w/ undo-redo functionality.1107*/1108void ConnectionsDock::_disconnect(const ConnectDialog::ConnectionData &p_cd) {1109ERR_FAIL_COND(p_cd.source != selected_object); // Shouldn't happen but... Bugcheck.11101111EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1112undo_redo->create_action(vformat(TTR("Disconnect '%s' from '%s'"), p_cd.signal, p_cd.method));11131114Callable callable = p_cd.get_callable();1115undo_redo->add_do_method(selected_object, "disconnect", p_cd.signal, callable);1116undo_redo->add_undo_method(selected_object, "connect", p_cd.signal, callable, p_cd.flags);1117undo_redo->add_do_method(this, "update_tree");1118undo_redo->add_undo_method(this, "update_tree");1119undo_redo->add_do_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree"); // To force redraw of scene tree.1120undo_redo->add_undo_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");11211122undo_redo->commit_action();1123}11241125/*1126* Break all connections of currently selected signal.1127* Can undo-redo as a single action.1128*/1129void ConnectionsDock::_disconnect_all() {1130TreeItem *item = tree->get_selected();1131if (!item || _get_item_type(*item) != TREE_ITEM_TYPE_SIGNAL) {1132return;1133}11341135TreeItem *child = item->get_first_child();1136String signal_name = item->get_metadata(0).operator Dictionary()["name"];1137EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1138undo_redo->create_action(vformat(TTR("Disconnect all from signal: '%s'"), signal_name));11391140while (child) {1141Connection connection = child->get_metadata(0);1142if (!_is_connection_inherited(connection)) {1143ConnectDialog::ConnectionData cd = connection;1144undo_redo->add_do_method(selected_object, "disconnect", cd.signal, cd.get_callable());1145undo_redo->add_undo_method(selected_object, "connect", cd.signal, cd.get_callable(), cd.flags);1146}1147child = child->get_next();1148}11491150undo_redo->add_do_method(this, "update_tree");1151undo_redo->add_undo_method(this, "update_tree");1152undo_redo->add_do_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");1153undo_redo->add_undo_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree");11541155undo_redo->commit_action();1156}11571158void ConnectionsDock::_tree_item_selected() {1159TreeItem *item = tree->get_selected();1160if (item && _get_item_type(*item) == TREE_ITEM_TYPE_SIGNAL) {1161connect_button->set_text(TTR("Connect..."));1162connect_button->set_button_icon(get_editor_theme_icon(SNAME("Instance")));1163connect_button->set_disabled(is_editing_resource);1164} else if (item && _get_item_type(*item) == TREE_ITEM_TYPE_CONNECTION) {1165connect_button->set_text(TTR("Disconnect"));1166connect_button->set_button_icon(get_editor_theme_icon(SNAME("Unlinked")));11671168Object::Connection connection = item->get_metadata(0);1169connect_button->set_disabled(_is_connection_inherited(connection));1170} else {1171connect_button->set_text(TTR("Connect..."));1172connect_button->set_button_icon(get_editor_theme_icon(SNAME("Instance")));1173connect_button->set_disabled(true);1174}1175}11761177void ConnectionsDock::_tree_item_activated() { // "Activation" on double-click.1178TreeItem *item = tree->get_selected();1179if (!item) {1180return;1181}11821183if (_get_item_type(*item) == TREE_ITEM_TYPE_SIGNAL) {1184_open_connection_dialog(*item);1185} else if (_get_item_type(*item) == TREE_ITEM_TYPE_CONNECTION) {1186_go_to_method(*item);1187}1188}11891190ConnectionsDock::TreeItemType ConnectionsDock::_get_item_type(const TreeItem &p_item) const {1191if (&p_item == tree->get_root()) {1192return TREE_ITEM_TYPE_ROOT;1193} else if (p_item.get_parent() == tree->get_root()) {1194return TREE_ITEM_TYPE_CLASS;1195} else if (p_item.get_parent()->get_parent() == tree->get_root()) {1196return TREE_ITEM_TYPE_SIGNAL;1197} else {1198return TREE_ITEM_TYPE_CONNECTION;1199}1200}12011202bool ConnectionsDock::_is_connection_inherited(Connection &p_connection) {1203return bool(p_connection.flags & CONNECT_INHERITED);1204}12051206/*1207* Open connection dialog with TreeItem data to CREATE a brand-new connection.1208*/1209void ConnectionsDock::_open_connection_dialog(TreeItem &p_item) {1210if (is_editing_resource) {1211return;1212}12131214const Dictionary sinfo = p_item.get_metadata(0);1215const StringName signal_name = sinfo["name"];1216const PackedStringArray signal_args = sinfo["args"];12171218ConnectDialog::ConnectionData cd;12191220Node *selected_node = Object::cast_to<Node>(selected_object);1221Node *dst_node = selected_node->get_owner() ? selected_node->get_owner() : selected_node;1222if (!dst_node || dst_node->get_script().is_null()) {1223dst_node = _find_first_script(get_tree()->get_edited_scene_root(), get_tree()->get_edited_scene_root());1224}1225cd.source = selected_object;1226cd.target = dst_node;1227cd.signal = signal_name;1228cd.method = ConnectDialog::generate_method_callback_name(cd.source, signal_name, cd.target);1229connect_dialog->init(cd, signal_args);1230connect_dialog->set_title(TTR("Connect a Signal to a Method"));1231connect_dialog->popup_dialog(signal_name.operator String() + "(" + String(", ").join(signal_args) + ")");1232}12331234/*1235* Open connection dialog with Connection data to EDIT an existing connection.1236*/1237void ConnectionsDock::_open_edit_connection_dialog(TreeItem &p_item) {1238TreeItem *signal_item = p_item.get_parent();1239ERR_FAIL_NULL(signal_item);12401241Connection connection = p_item.get_metadata(0);1242ConnectDialog::ConnectionData cd = connection;12431244Object *src = cd.source;1245Object *dst = cd.target;12461247if (src && dst) {1248const StringName &signal_name = cd.signal;1249const PackedStringArray signal_args = signal_item->get_metadata(0).operator Dictionary()["args"];12501251connect_dialog->init(cd, signal_args, true);1252connect_dialog->set_title(vformat(TTR("Edit Connection: '%s'"), cd.signal));1253connect_dialog->popup_dialog(signal_name.operator String() + "(" + String(", ").join(signal_args) + ")");1254}1255}12561257/*1258* Open slot method location in script editor.1259*/1260void ConnectionsDock::_go_to_method(TreeItem &p_item) {1261if (_get_item_type(p_item) != TREE_ITEM_TYPE_CONNECTION) {1262return;1263}12641265Connection connection = p_item.get_metadata(0);1266ConnectDialog::ConnectionData cd = connection;1267ERR_FAIL_COND(cd.source != selected_object); // Shouldn't happen but... bugcheck.12681269if (!cd.target) {1270return;1271}12721273Ref<Script> scr = cd.target->get_script();12741275if (scr.is_null()) {1276return;1277}12781279if (scr.is_valid() && ScriptEditor::get_singleton()->script_goto_method(scr, cd.method)) {1280EditorNode::get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);1281}1282}12831284void ConnectionsDock::_handle_class_menu_option(int p_option) {1285switch (p_option) {1286case CLASS_MENU_OPEN_DOCS:1287ScriptEditor::get_singleton()->goto_help("class:" + class_menu_doc_class_name);1288EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);1289break;1290}1291}12921293void ConnectionsDock::_class_menu_about_to_popup() {1294class_menu->set_item_disabled(class_menu->get_item_index(CLASS_MENU_OPEN_DOCS), class_menu_doc_class_name.is_empty());1295}12961297void ConnectionsDock::_handle_signal_menu_option(int p_option) {1298TreeItem *item = tree->get_selected();1299if (!item || _get_item_type(*item) != TREE_ITEM_TYPE_SIGNAL) {1300return;1301}13021303Dictionary meta = item->get_metadata(0);13041305switch (p_option) {1306case SIGNAL_MENU_CONNECT: {1307_open_connection_dialog(*item);1308} break;1309case SIGNAL_MENU_DISCONNECT_ALL: {1310disconnect_all_dialog->set_text(vformat(TTR("Are you sure you want to remove all connections from the \"%s\" signal?"), meta["name"]));1311disconnect_all_dialog->popup_centered();1312} break;1313case SIGNAL_MENU_COPY_NAME: {1314DisplayServer::get_singleton()->clipboard_set(meta["name"]);1315} break;1316case SIGNAL_MENU_OPEN_DOCS: {1317ScriptEditor::get_singleton()->goto_help("class_signal:" + String(meta["class"]) + ":" + String(meta["name"]));1318EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);1319} break;1320}1321}13221323void ConnectionsDock::_signal_menu_about_to_popup() {1324TreeItem *item = tree->get_selected();1325if (!item || _get_item_type(*item) != TREE_ITEM_TYPE_SIGNAL) {1326return;1327}13281329Dictionary meta = item->get_metadata(0);13301331bool disable_disconnect_all = true;1332for (int i = 0; i < item->get_child_count(); i++) {1333if (!item->get_child(i)->has_meta("_inherited_connection")) {1334disable_disconnect_all = false;1335}1336}13371338signal_menu->set_item_disabled(signal_menu->get_item_index(SIGNAL_MENU_CONNECT), is_editing_resource);1339signal_menu->set_item_disabled(signal_menu->get_item_index(SIGNAL_MENU_DISCONNECT_ALL), disable_disconnect_all);1340signal_menu->set_item_disabled(signal_menu->get_item_index(SIGNAL_MENU_OPEN_DOCS), String(meta["class"]).is_empty());1341}13421343void ConnectionsDock::_handle_slot_menu_option(int p_option) {1344TreeItem *item = tree->get_selected();1345if (!item || _get_item_type(*item) != TREE_ITEM_TYPE_CONNECTION) {1346return;1347}13481349switch (p_option) {1350case SLOT_MENU_EDIT: {1351_open_edit_connection_dialog(*item);1352} break;1353case SLOT_MENU_GO_TO_METHOD: {1354_go_to_method(*item);1355} break;1356case SLOT_MENU_DISCONNECT: {1357Connection connection = item->get_metadata(0);1358_disconnect(connection);1359update_tree();1360} break;1361}1362}13631364void ConnectionsDock::_slot_menu_about_to_popup() {1365TreeItem *item = tree->get_selected();1366if (!item || _get_item_type(*item) != TREE_ITEM_TYPE_CONNECTION) {1367return;1368}13691370bool connection_is_inherited = item->has_meta("_inherited_connection");13711372slot_menu->set_item_disabled(slot_menu->get_item_index(SLOT_MENU_EDIT), connection_is_inherited);1373slot_menu->set_item_disabled(slot_menu->get_item_index(SLOT_MENU_DISCONNECT), connection_is_inherited);1374}13751376void ConnectionsDock::_tree_gui_input(const Ref<InputEvent> &p_event) {1377TreeItem *item = nullptr;1378Point2 item_pos;13791380const Ref<InputEventKey> &key = p_event;13811382if (key.is_valid() && key->is_pressed() && !key->is_echo()) {1383if (ED_IS_SHORTCUT("connections_editor/disconnect", p_event)) {1384item = tree->get_selected();1385if (item && _get_item_type(*item) == TREE_ITEM_TYPE_CONNECTION) {1386Connection connection = item->get_metadata(0);1387_disconnect(connection);1388update_tree();13891390// Stop the Delete input from propagating elsewhere.1391accept_event();1392return;1393}1394} else if (ED_IS_SHORTCUT("editor/open_search", p_event)) {1395search_box->grab_focus();1396search_box->select_all();13971398accept_event();1399return;1400}1401}1402if (key.is_valid() && key->is_pressed() && key->is_action("ui_menu", true)) {1403item = tree->get_selected();1404if (!item) {1405return;1406}1407item_pos = tree->get_item_rect(item).position;1408}14091410// Handle RMB press.1411const Ref<InputEventMouseButton> &mb_event = p_event;14121413if (mb_event.is_valid() && mb_event->is_pressed() && mb_event->get_button_index() == MouseButton::RIGHT) {1414item = tree->get_item_at_position(mb_event->get_position());1415if (!item) {1416return;1417}1418item_pos = mb_event->get_position();1419}14201421if (item) {1422if (item->is_selectable(0)) {1423// Update selection now, before `about_to_popup` signal. Needed for SIGNAL and CONNECTION context menus.1424tree->set_selected(item);1425}14261427Vector2 screen_position = tree->get_screen_position() + item_pos;14281429switch (_get_item_type(*item)) {1430case TREE_ITEM_TYPE_ROOT:1431break;1432case TREE_ITEM_TYPE_CLASS:1433class_menu_doc_class_name = item->get_metadata(0);1434class_menu->set_position(screen_position);1435class_menu->reset_size();1436class_menu->popup();1437accept_event(); // Don't collapse item.1438break;1439case TREE_ITEM_TYPE_SIGNAL:1440signal_menu->set_position(screen_position);1441signal_menu->reset_size();1442signal_menu->popup();1443break;1444case TREE_ITEM_TYPE_CONNECTION:1445slot_menu->set_position(screen_position);1446slot_menu->reset_size();1447slot_menu->popup();1448break;1449}1450}1451}14521453void ConnectionsDock::_close() {1454hide();1455}14561457void ConnectionsDock::_connect_pressed() {1458TreeItem *item = tree->get_selected();1459if (!item) {1460connect_button->set_disabled(true);1461return;1462}14631464if (_get_item_type(*item) == TREE_ITEM_TYPE_SIGNAL) {1465_open_connection_dialog(*item);1466} else if (_get_item_type(*item) == TREE_ITEM_TYPE_CONNECTION) {1467Connection connection = item->get_metadata(0);1468_disconnect(connection);1469update_tree();1470}1471}14721473void ConnectionsDock::_notification(int p_what) {1474switch (p_what) {1475case NOTIFICATION_THEME_CHANGED: {1476search_box->set_right_icon(get_editor_theme_icon(SNAME("Search")));14771478class_menu->set_item_icon(class_menu->get_item_index(CLASS_MENU_OPEN_DOCS), get_editor_theme_icon(SNAME("Help")));14791480signal_menu->set_item_icon(signal_menu->get_item_index(SIGNAL_MENU_CONNECT), get_editor_theme_icon(SNAME("Instance")));1481signal_menu->set_item_icon(signal_menu->get_item_index(SIGNAL_MENU_DISCONNECT_ALL), get_editor_theme_icon(SNAME("Unlinked")));1482signal_menu->set_item_icon(signal_menu->get_item_index(SIGNAL_MENU_COPY_NAME), get_editor_theme_icon(SNAME("ActionCopy")));1483signal_menu->set_item_icon(signal_menu->get_item_index(SIGNAL_MENU_OPEN_DOCS), get_editor_theme_icon(SNAME("Help")));14841485slot_menu->set_item_icon(slot_menu->get_item_index(SLOT_MENU_EDIT), get_editor_theme_icon(SNAME("Edit")));1486slot_menu->set_item_icon(slot_menu->get_item_index(SLOT_MENU_GO_TO_METHOD), get_editor_theme_icon(SNAME("ArrowRight")));1487slot_menu->set_item_icon(slot_menu->get_item_index(SLOT_MENU_DISCONNECT), get_editor_theme_icon(SNAME("Unlinked")));14881489tree->add_theme_constant_override("icon_max_width", get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor)));14901491update_tree();1492} break;14931494case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {1495if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editors")) {1496update_tree();1497}1498} break;1499}1500}15011502void ConnectionsDock::_bind_methods() {1503ClassDB::bind_method("update_tree", &ConnectionsDock::update_tree);1504}15051506void ConnectionsDock::set_object(Object *p_object) {1507if (p_object == nullptr) {1508select_an_object->show();1509holder->hide();1510} else {1511select_an_object->hide();1512holder->show();1513}1514selected_object = p_object;1515is_editing_resource = (Object::cast_to<Resource>(selected_object) != nullptr);1516update_tree();1517}15181519void ConnectionsDock::update_tree() {1520String prev_selected;1521if (tree->is_anything_selected()) {1522prev_selected = tree->get_selected()->get_text(0);1523}1524tree->clear();15251526if (!selected_object) {1527return;1528}15291530TreeItem *root = tree->create_item();1531DocTools *doc_data = EditorHelp::get_doc_data();1532EditorData &editor_data = EditorNode::get_editor_data();1533StringName native_base = selected_object->get_class();1534Ref<Script> script_base = selected_object->get_script();15351536while (native_base != StringName()) {1537String class_name;1538String doc_class_name;1539Ref<Texture2D> class_icon;1540List<MethodInfo> class_signals;15411542if (script_base.is_valid()) {1543class_name = script_base->get_global_name();1544if (class_name.is_empty()) {1545class_name = script_base->get_path().get_file();1546}15471548doc_class_name = script_base->get_global_name();1549if (doc_class_name.is_empty()) {1550doc_class_name = script_base->get_path().trim_prefix("res://").quote();1551}1552if (!doc_class_name.is_empty() && !doc_data->class_list.find(doc_class_name)) {1553doc_class_name = String();1554}15551556class_icon = editor_data.get_script_icon(script_base->get_path());1557if (class_icon.is_null() && has_theme_icon(native_base, EditorStringName(EditorIcons))) {1558class_icon = get_editor_theme_icon(native_base);1559}15601561script_base->get_script_signal_list(&class_signals);15621563// TODO: Core: Add optional parameter to ignore base classes (no_inheritance like in ClassDB).1564Ref<Script> base = script_base->get_base_script();1565if (base.is_valid()) {1566List<MethodInfo> base_signals;1567base->get_script_signal_list(&base_signals);1568HashSet<String> base_signal_names;1569for (const MethodInfo &signal : base_signals) {1570base_signal_names.insert(signal.name);1571}1572for (List<MethodInfo>::Element *F = class_signals.front(); F;) {1573List<MethodInfo>::Element *N = F->next();1574if (base_signal_names.has(F->get().name)) {1575class_signals.erase(F);1576}1577F = N;1578}1579}15801581script_base = base;1582} else {1583class_name = native_base;1584doc_class_name = native_base;15851586if (!doc_data->class_list.find(doc_class_name)) {1587doc_class_name = String();1588}15891590if (has_theme_icon(native_base, EditorStringName(EditorIcons))) {1591class_icon = get_editor_theme_icon(native_base);1592}15931594ClassDB::get_signal_list(native_base, &class_signals, true);15951596native_base = ClassDB::get_parent_class(native_base);1597}15981599if (class_icon.is_null()) {1600class_icon = get_editor_theme_icon(SNAME("Object"));1601}16021603TreeItem *section_item = nullptr;16041605// Create subsections.1606if (!class_signals.is_empty()) {1607class_signals.sort();16081609section_item = tree->create_item(root);1610section_item->set_text(0, class_name);1611// `|` separators used in `EditorHelpBit`.1612section_item->set_tooltip_text(0, "class|" + doc_class_name + "|");1613section_item->set_icon(0, class_icon);1614section_item->set_selectable(0, false);1615section_item->set_editable(0, false);1616section_item->set_custom_bg_color(0, get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor)));1617section_item->set_custom_stylebox(0, get_theme_stylebox(SNAME("prop_subsection_stylebox"), EditorStringName(Editor)));1618section_item->set_metadata(0, doc_class_name);1619}16201621for (MethodInfo &mi : class_signals) {1622const StringName &signal_name = mi.name;1623if (!search_box->get_text().is_subsequence_ofn(signal_name)) {1624continue;1625}1626PackedStringArray argnames;16271628// Create the children of the subsection - the actual list of signals.1629TreeItem *signal_item = tree->create_item(section_item);1630String signame = connect_dialog->get_signature(mi, &argnames);1631signal_item->set_text(0, signame);16321633if (signame == prev_selected) {1634signal_item->select(0);1635prev_selected = "";1636}16371638Dictionary sinfo;1639sinfo["class"] = doc_class_name;1640sinfo["name"] = signal_name;1641sinfo["args"] = argnames;1642signal_item->set_metadata(0, sinfo);1643signal_item->set_icon(0, get_editor_theme_icon(SNAME("Signal")));1644// `|` separators used in `EditorHelpBit`.1645signal_item->set_tooltip_text(0, "signal|" + doc_class_name + "|" + String(signal_name));16461647// List existing connections.1648List<Object::Connection> existing_connections;1649selected_object->get_signal_connection_list(signal_name, &existing_connections);16501651for (const Object::Connection &F : existing_connections) {1652Connection connection = F;1653if (!(connection.flags & CONNECT_PERSIST)) {1654continue;1655}1656ConnectDialog::ConnectionData cd = connection;16571658Node *target = Object::cast_to<Node>(cd.target);1659if (!target) {1660continue;1661}16621663String path = String(Object::cast_to<Node>(selected_object)->get_path_to(target)) + " :: " + cd.method + "()";1664if (cd.flags & CONNECT_DEFERRED) {1665path += " (deferred)";1666}1667if (cd.flags & CONNECT_ONE_SHOT) {1668path += " (one-shot)";1669}1670if (cd.unbinds > 0) {1671path += " unbinds(" + itos(cd.unbinds) + ")";1672}1673// CONNECT_APPEND_SOURCE_OBJECT is not affected by unbinds, list it between unbinds/binds to better indicate the final order.1674if (cd.flags & CONNECT_APPEND_SOURCE_OBJECT) {1675path += " (source)";1676}1677if (!cd.binds.is_empty()) {1678path += " binds(";1679for (int i = 0; i < cd.binds.size(); i++) {1680if (i > 0) {1681path += ", ";1682}1683path += cd.binds[i].operator String();1684}1685path += ")";1686}16871688TreeItem *connection_item = tree->create_item(signal_item);1689connection_item->set_text(0, path);1690connection_item->set_metadata(0, connection);1691connection_item->set_icon(0, get_editor_theme_icon(SNAME("Slot")));16921693if (_is_connection_inherited(connection)) {1694// The scene inherits this connection.1695connection_item->set_custom_color(0, get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));1696connection_item->set_meta("_inherited_connection", true);1697}1698}1699}1700}17011702connect_button->set_text(TTRC("Connect..."));1703connect_button->set_button_icon(get_editor_theme_icon(SNAME("Instance")));1704connect_button->set_disabled(true);1705}17061707ConnectionsDock::ConnectionsDock() {1708set_name(TTR("Signals"));17091710holder = memnew(VBoxContainer);1711holder->set_v_size_flags(SIZE_EXPAND_FILL);1712holder->hide();1713add_child(holder);17141715search_box = memnew(LineEdit);1716search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);1717search_box->set_placeholder(TTR("Filter Signals"));1718search_box->set_accessibility_name(TTRC("Filter Signals"));1719search_box->set_clear_button_enabled(true);1720search_box->connect(SceneStringName(text_changed), callable_mp(this, &ConnectionsDock::_filter_changed));1721holder->add_child(search_box);17221723MarginContainer *mc = memnew(MarginContainer);1724mc->set_theme_type_variation("NoBorderHorizontal");1725mc->set_v_size_flags(SIZE_EXPAND_FILL);1726holder->add_child(mc);17271728tree = memnew(ConnectionsDockTree);1729tree->set_accessibility_name(TTRC("Connections"));1730tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);1731tree->set_columns(1);1732tree->set_select_mode(Tree::SELECT_ROW);1733tree->set_hide_root(true);1734tree->set_allow_rmb_select(true);1735tree->set_column_clip_content(0, true);1736tree->set_scroll_hint_mode(Tree::SCROLL_HINT_MODE_BOTH);1737mc->add_child(tree);17381739connect_button = memnew(Button);1740connect_button->set_accessibility_name(TTRC("Connect"));1741HBoxContainer *hb = memnew(HBoxContainer);1742holder->add_child(hb);1743hb->add_spacer();1744hb->add_child(connect_button);1745connect_button->connect(SceneStringName(pressed), callable_mp(this, &ConnectionsDock::_connect_pressed));17461747connect_dialog = memnew(ConnectDialog);1748connect_dialog->set_process_shortcut_input(true);1749holder->add_child(connect_dialog);17501751disconnect_all_dialog = memnew(ConfirmationDialog);1752holder->add_child(disconnect_all_dialog);1753disconnect_all_dialog->connect(SceneStringName(confirmed), callable_mp(this, &ConnectionsDock::_disconnect_all));1754disconnect_all_dialog->set_text(TTR("Are you sure you want to remove all connections from this signal?"));17551756class_menu = memnew(PopupMenu);1757class_menu->connect(SceneStringName(id_pressed), callable_mp(this, &ConnectionsDock::_handle_class_menu_option));1758class_menu->connect("about_to_popup", callable_mp(this, &ConnectionsDock::_class_menu_about_to_popup));1759class_menu->add_item(TTR("Open Documentation"), CLASS_MENU_OPEN_DOCS);1760holder->add_child(class_menu);17611762signal_menu = memnew(PopupMenu);1763signal_menu->connect(SceneStringName(id_pressed), callable_mp(this, &ConnectionsDock::_handle_signal_menu_option));1764signal_menu->connect("about_to_popup", callable_mp(this, &ConnectionsDock::_signal_menu_about_to_popup));1765signal_menu->add_item(TTR("Connect..."), SIGNAL_MENU_CONNECT);1766signal_menu->add_item(TTR("Disconnect All"), SIGNAL_MENU_DISCONNECT_ALL);1767signal_menu->add_item(TTR("Copy Name"), SIGNAL_MENU_COPY_NAME);1768signal_menu->add_separator();1769signal_menu->add_item(TTR("Open Documentation"), SIGNAL_MENU_OPEN_DOCS);1770holder->add_child(signal_menu);17711772slot_menu = memnew(PopupMenu);1773slot_menu->connect(SceneStringName(id_pressed), callable_mp(this, &ConnectionsDock::_handle_slot_menu_option));1774slot_menu->connect("about_to_popup", callable_mp(this, &ConnectionsDock::_slot_menu_about_to_popup));1775slot_menu->add_item(TTR("Edit..."), SLOT_MENU_EDIT);1776slot_menu->add_item(TTR("Go to Method"), SLOT_MENU_GO_TO_METHOD);1777slot_menu->add_shortcut(ED_SHORTCUT("connections_editor/disconnect", TTRC("Disconnect"), Key::KEY_DELETE), SLOT_MENU_DISCONNECT);1778holder->add_child(slot_menu);17791780connect_dialog->connect("connected", callable_mp(this, &ConnectionsDock::_make_or_edit_connection));1781tree->connect(SceneStringName(item_selected), callable_mp(this, &ConnectionsDock::_tree_item_selected));1782tree->connect("item_activated", callable_mp(this, &ConnectionsDock::_tree_item_activated));1783tree->connect(SceneStringName(gui_input), callable_mp(this, &ConnectionsDock::_tree_gui_input));17841785add_theme_constant_override("separation", 3 * EDSCALE);17861787select_an_object = memnew(Label);1788select_an_object->set_focus_mode(FOCUS_ACCESSIBILITY);1789select_an_object->set_text(TTRC("Select a single node or resource to edit its signals."));1790select_an_object->set_custom_minimum_size(Size2(100 * EDSCALE, 0));1791select_an_object->set_v_size_flags(SIZE_EXPAND_FILL);1792select_an_object->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);1793select_an_object->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);1794select_an_object->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);1795add_child(select_an_object);1796}179717981799