/*************************************************************************/ /* connections_dialog.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* 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 "connections_dialog.h" #include "core/containers/list.h" #include "core/error/error_macros.h" #include "core/math/aabb.h" #include "core/math/basis.h" #include "core/math/color.h" #include "core/math/plane.h" #include "core/math/quaternion.h" #include "core/math/rect2.h" #include "core/math/transform.h" #include "core/math/vector2.h" #include "core/math/vector3.h" #include "core/object/class_db.h" #include "core/object/ref_ptr.h" #include "core/object/reference.h" #include "core/object/script_language.h" #include "core/object/undo_redo.h" #include "core/os/memory.h" #include "core/typedefs.h" #include "core/variant/dictionary.h" #include "core/variant/variant.h" #include "editor/doc/doc_data.h" #include "editor/editor_help.h" #include "editor/editor_inspector.h" #include "editor/scene_tree_dock.h" #include "editor/scene_tree_editor.h" #include "editor_node.h" #include "editor_scale.h" #include "editor_settings.h" #include "scene/gui/button.h" #include "scene/gui/check_box.h" #include "scene/gui/check_button.h" #include "scene/main/control.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" #include "scene/gui/margin_container.h" #include "scene/gui/option_button.h" #include "scene/gui/popup_menu.h" #include "scene/gui/rich_text_label.h" #include "scene/main/node.h" #include "scene/main/scene_tree.h" #include "scene/resources/texture.h" #include "modules/modules_enabled.gen.h" #ifdef MODULE_EDITOR_CODE_EDITOR_ENABLED #include "editor_code_editor/editor_script_editor.h" #endif static Node *_find_first_script(Node *p_root, Node *p_node) { if (p_node != p_root && p_node->get_owner() != p_root) { return nullptr; } if (!p_node->get_script().is_null()) { return p_node; } for (int i = 0; i < p_node->get_child_count(); i++) { Node *ret = _find_first_script(p_root, p_node->get_child(i)); if (ret) { return ret; } } return nullptr; } class ConnectDialogBinds : public Object { GDCLASS(ConnectDialogBinds, Object); public: Vector params; bool _set(const StringName &p_name, const Variant &p_value) { String name = p_name; if (name.begins_with("bind/argument_")) { int which = name.get_slice("_", 1).to_int() - 1; ERR_FAIL_INDEX_V(which, params.size(), false); params.write[which] = p_value; } else { return false; } return true; } bool _get(const StringName &p_name, Variant &r_ret) const { String name = p_name; if (name.begins_with("bind/argument_")) { int which = name.get_slice("_", 1).to_int() - 1; ERR_FAIL_INDEX_V(which, params.size(), false); r_ret = params[which]; } else { return false; } return true; } void _get_property_list(List *p_list) const { for (int i = 0; i < params.size(); i++) { p_list->push_back(PropertyInfo(params[i].get_type(), "bind/argument_" + itos(i + 1))); } } void notify_changed() { _change_notify(); } ConnectDialogBinds() { } }; /* * Signal automatically called by parent dialog. */ void ConnectDialog::ok_pressed() { String method_name = dst_method->get_text(); if (method_name == "") { error->set_text(TTR("Method in target node must be specified.")); error->popup_centered_minsize(); return; } if (!method_name.strip_edges().is_valid_identifier()) { error->set_text(TTR("Method name must be a valid identifier.")); error->popup_centered(); return; } Node *target = tree->get_selected(); if (!target) { return; // Nothing selected in the tree, not an error. } if (target->get_script().is_null()) { if (!target->has_method(method_name)) { error->set_text(TTR("Target method not found. Specify a valid method or attach a script to the target node.")); error->popup_centered_minsize(); return; } } emit_signal("connected"); hide(); } void ConnectDialog::_cancel_pressed() { hide(); } /* * Called each time a target node is selected within the target node tree. */ void ConnectDialog::_tree_node_selected() { Node *current = tree->get_selected(); if (!current) { return; } dst_path = source->get_path_to(current); _update_ok_enabled(); } /* * Adds a new parameter bind to connection. */ void ConnectDialog::_add_bind() { if (cdbinds->params.size() >= VARIANT_ARG_MAX) { return; } Variant::Type vt = (Variant::Type)type_list->get_item_id(type_list->get_selected()); Variant value; switch (vt) { case Variant::BOOL: value = false; break; case Variant::INT: value = 0; break; case Variant::REAL: value = 0.0; break; case Variant::STRING: value = ""; break; case Variant::STRING_NAME: value = ""; break; case Variant::VECTOR2: value = Vector2(); break; case Variant::RECT2: value = Rect2(); break; case Variant::VECTOR3: value = Vector3(); break; case Variant::PLANE: value = Plane(); break; case Variant::QUATERNION: value = Quaternion(); break; case Variant::AABB: value = AABB(); break; case Variant::BASIS: value = Basis(); break; case Variant::TRANSFORM: value = Transform(); break; case Variant::COLOR: value = Color(); break; default: { ERR_FAIL(); } break; } ERR_FAIL_COND(value.get_type() == Variant::NIL); cdbinds->params.push_back(value); cdbinds->notify_changed(); } /* * Remove parameter bind from connection. */ void ConnectDialog::_remove_bind() { String st = bind_editor->get_selected_path(); if (st == "") { return; } int idx = st.get_slice("/", 1).to_int() - 1; ERR_FAIL_INDEX(idx, cdbinds->params.size()); cdbinds->params.remove(idx); cdbinds->notify_changed(); } /* * Enables or disables the connect button. The connect button is enabled if a * node is selected and valid in the selected mode. */ void ConnectDialog::_update_ok_enabled() { Node *target = tree->get_selected(); if (target == nullptr) { get_ok()->set_disabled(true); return; } if (!advanced->is_pressed() && target->get_script().is_null()) { get_ok()->set_disabled(true); return; } get_ok()->set_disabled(false); } void ConnectDialog::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { bind_editor->edit(cdbinds); } } void ConnectDialog::_bind_methods() { ClassDB::bind_method("_advanced_pressed", &ConnectDialog::_advanced_pressed); ClassDB::bind_method("_cancel", &ConnectDialog::_cancel_pressed); ClassDB::bind_method("_tree_node_selected", &ConnectDialog::_tree_node_selected); ClassDB::bind_method("_add_bind", &ConnectDialog::_add_bind); ClassDB::bind_method("_remove_bind", &ConnectDialog::_remove_bind); ClassDB::bind_method("_update_ok_enabled", &ConnectDialog::_update_ok_enabled); ADD_SIGNAL(MethodInfo("connected")); } Node *ConnectDialog::get_source() const { return source; } StringName ConnectDialog::get_signal_name() const { return signal; } NodePath ConnectDialog::get_dst_path() const { return dst_path; } void ConnectDialog::set_dst_node(Node *p_node) { tree->set_selected(p_node); } StringName ConnectDialog::get_dst_method_name() const { String txt = dst_method->get_text(); if (txt.find("(") != -1) { txt = txt.left(txt.find("(")).strip_edges(); } return txt; } void ConnectDialog::set_dst_method(const StringName &p_method) { dst_method->set_text(p_method); } Vector ConnectDialog::get_binds() const { return cdbinds->params; } bool ConnectDialog::get_deferred() const { return deferred->is_pressed(); } bool ConnectDialog::get_oneshot() const { return oneshot->is_pressed(); } /* * Returns true if ConnectDialog is being used to edit an existing connection. */ bool ConnectDialog::is_editing() const { return bEditMode; } /* * Initialize ConnectDialog and populate fields with expected data. * If creating a connection from scratch, sensible defaults are used. * If editing an existing connection, previous data is retained. */ void ConnectDialog::init(Connection c, bool bEdit) { set_hide_on_ok(false); source = static_cast(c.source); signal = c.signal; tree->set_selected(nullptr); tree->set_marked(source, true); if (c.target) { set_dst_node(static_cast(c.target)); set_dst_method(c.method); } _update_ok_enabled(); bool bDeferred = (c.flags & CONNECT_DEFERRED) == CONNECT_DEFERRED; bool bOneshot = (c.flags & CONNECT_ONESHOT) == CONNECT_ONESHOT; deferred->set_pressed(bDeferred); oneshot->set_pressed(bOneshot); cdbinds->params.clear(); cdbinds->params = c.binds; cdbinds->notify_changed(); bEditMode = bEdit; } void ConnectDialog::popup_dialog(const String &p_for_signal) { from_signal->set_text(p_for_signal); error_label->add_theme_color_override("font_color", get_theme_color("error_color", "Editor")); if (!advanced->is_pressed()) { error_label->set_visible(!_find_first_script(get_tree()->get_edited_scene_root(), get_tree()->get_edited_scene_root())); } popup_centered(); } void ConnectDialog::_advanced_pressed() { if (advanced->is_pressed()) { set_custom_minimum_size(Size2(900, 500) * EDSCALE); connect_to_label->set_text(TTR("Connect to Node:")); tree->set_connect_to_script_mode(false); vbc_right->show(); error_label->hide(); } else { set_custom_minimum_size(Size2(600, 500) * EDSCALE); set_size(Size2()); connect_to_label->set_text(TTR("Connect to Script:")); tree->set_connect_to_script_mode(true); vbc_right->hide(); error_label->set_visible(!_find_first_script(get_tree()->get_edited_scene_root(), get_tree()->get_edited_scene_root())); } _update_ok_enabled(); set_position((get_viewport_rect().size - get_custom_minimum_size()) / 2); } ConnectDialog::ConnectDialog() { set_custom_minimum_size(Size2(600, 500) * EDSCALE); VBoxContainer *vbc = memnew(VBoxContainer); add_child(vbc); HBoxContainer *main_hb = memnew(HBoxContainer); vbc->add_child(main_hb); main_hb->set_v_size_flags(SIZE_EXPAND_FILL); VBoxContainer *vbc_left = memnew(VBoxContainer); main_hb->add_child(vbc_left); vbc_left->set_h_size_flags(SIZE_EXPAND_FILL); from_signal = memnew(LineEdit); from_signal->set_editable(false); vbc_left->add_margin_child(TTR("From Signal:"), from_signal); tree = memnew(SceneTreeEditor(false)); tree->set_connecting_signal(true); tree->set_show_enabled_subscene(true); tree->get_scene_tree()->connect("item_activated", this, "_ok"); tree->connect("node_selected", this, "_tree_node_selected"); tree->set_connect_to_script_mode(true); Node *mc = vbc_left->add_margin_child(TTR("Connect to Script:"), tree, true); connect_to_label = Object::cast_to