1 /*************************************************************************/
2 /* connections_dialog.cpp */
3 /*************************************************************************/
4 /* This file is part of: */
5 /* GODOT ENGINE */
6 /* https://godotengine.org */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
9 /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
10 /* */
11 /* Permission is hereby granted, free of charge, to any person obtaining */
12 /* a copy of this software and associated documentation files (the */
13 /* "Software"), to deal in the Software without restriction, including */
14 /* without limitation the rights to use, copy, modify, merge, publish, */
15 /* distribute, sublicense, and/or sell copies of the Software, and to */
16 /* permit persons to whom the Software is furnished to do so, subject to */
17 /* the following conditions: */
18 /* */
19 /* The above copyright notice and this permission notice shall be */
20 /* included in all copies or substantial portions of the Software. */
21 /* */
22 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24 /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
25 /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26 /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29 /*************************************************************************/
30
31 #include "connections_dialog.h"
32
33 #include "core/print_string.h"
34 #include "editor_node.h"
35 #include "editor_scale.h"
36 #include "editor_settings.h"
37 #include "plugins/script_editor_plugin.h"
38 #include "scene/gui/label.h"
39 #include "scene/gui/popup_menu.h"
40
_find_first_script(Node * p_root,Node * p_node)41 static Node *_find_first_script(Node *p_root, Node *p_node) {
42 if (p_node != p_root && p_node->get_owner() != p_root) {
43 return NULL;
44 }
45 if (!p_node->get_script().is_null()) {
46 return p_node;
47 }
48
49 for (int i = 0; i < p_node->get_child_count(); i++) {
50
51 Node *ret = _find_first_script(p_root, p_node->get_child(i));
52 if (ret) {
53 return ret;
54 }
55 }
56
57 return NULL;
58 }
59
60 class ConnectDialogBinds : public Object {
61
62 GDCLASS(ConnectDialogBinds, Object);
63
64 public:
65 Vector<Variant> params;
66
_set(const StringName & p_name,const Variant & p_value)67 bool _set(const StringName &p_name, const Variant &p_value) {
68
69 String name = p_name;
70
71 if (name.begins_with("bind/")) {
72 int which = name.get_slice("/", 1).to_int() - 1;
73 ERR_FAIL_INDEX_V(which, params.size(), false);
74 params.write[which] = p_value;
75 } else
76 return false;
77
78 return true;
79 }
80
_get(const StringName & p_name,Variant & r_ret) const81 bool _get(const StringName &p_name, Variant &r_ret) const {
82
83 String name = p_name;
84
85 if (name.begins_with("bind/")) {
86 int which = name.get_slice("/", 1).to_int() - 1;
87 ERR_FAIL_INDEX_V(which, params.size(), false);
88 r_ret = params[which];
89 } else
90 return false;
91
92 return true;
93 }
94
_get_property_list(List<PropertyInfo> * p_list) const95 void _get_property_list(List<PropertyInfo> *p_list) const {
96
97 for (int i = 0; i < params.size(); i++) {
98 p_list->push_back(PropertyInfo(params[i].get_type(), "bind/" + itos(i + 1)));
99 }
100 }
101
notify_changed()102 void notify_changed() {
103
104 _change_notify();
105 }
106
ConnectDialogBinds()107 ConnectDialogBinds() {
108 }
109 };
110
111 /*
112 * Signal automatically called by parent dialog.
113 */
ok_pressed()114 void ConnectDialog::ok_pressed() {
115
116 String method_name = dst_method->get_text();
117
118 if (method_name == "") {
119 error->set_text(TTR("Method in target node must be specified."));
120 error->popup_centered_minsize();
121 return;
122 }
123
124 if (!method_name.strip_edges().is_valid_identifier()) {
125 error->set_text(TTR("Method name must be a valid identifier."));
126 error->popup_centered();
127 return;
128 }
129
130 Node *target = tree->get_selected();
131 if (!target) {
132 return; // Nothing selected in the tree, not an error.
133 }
134 if (target->get_script().is_null()) {
135 if (!target->has_method(method_name)) {
136 error->set_text(TTR("Target method not found. Specify a valid method or attach a script to the target node."));
137 error->popup_centered_minsize();
138 return;
139 }
140 }
141 emit_signal("connected");
142 hide();
143 }
144
_cancel_pressed()145 void ConnectDialog::_cancel_pressed() {
146
147 hide();
148 }
149
150 /*
151 * Called each time a target node is selected within the target node tree.
152 */
_tree_node_selected()153 void ConnectDialog::_tree_node_selected() {
154
155 Node *current = tree->get_selected();
156
157 if (!current)
158 return;
159
160 dst_path = source->get_path_to(current);
161 _update_ok_enabled();
162 }
163
164 /*
165 * Adds a new parameter bind to connection.
166 */
_add_bind()167 void ConnectDialog::_add_bind() {
168
169 if (cdbinds->params.size() >= VARIANT_ARG_MAX)
170 return;
171 Variant::Type vt = (Variant::Type)type_list->get_item_id(type_list->get_selected());
172
173 Variant value;
174
175 switch (vt) {
176 case Variant::BOOL: value = false; break;
177 case Variant::INT: value = 0; break;
178 case Variant::REAL: value = 0.0; break;
179 case Variant::STRING: value = ""; break;
180 case Variant::VECTOR2: value = Vector2(); break;
181 case Variant::RECT2: value = Rect2(); break;
182 case Variant::VECTOR3: value = Vector3(); break;
183 case Variant::PLANE: value = Plane(); break;
184 case Variant::QUAT: value = Quat(); break;
185 case Variant::AABB: value = AABB(); break;
186 case Variant::BASIS: value = Basis(); break;
187 case Variant::TRANSFORM: value = Transform(); break;
188 case Variant::COLOR: value = Color(); break;
189 default: {
190 ERR_FAIL();
191 } break;
192 }
193
194 ERR_FAIL_COND(value.get_type() == Variant::NIL);
195
196 cdbinds->params.push_back(value);
197 cdbinds->notify_changed();
198 }
199
200 /*
201 * Remove parameter bind from connection.
202 */
_remove_bind()203 void ConnectDialog::_remove_bind() {
204
205 String st = bind_editor->get_selected_path();
206 if (st == "")
207 return;
208 int idx = st.get_slice("/", 1).to_int() - 1;
209
210 ERR_FAIL_INDEX(idx, cdbinds->params.size());
211 cdbinds->params.remove(idx);
212 cdbinds->notify_changed();
213 }
214
215 /*
216 * Enables or disables the connect button. The connect button is enabled if a
217 * node is selected and valid in the selected mode.
218 */
_update_ok_enabled()219 void ConnectDialog::_update_ok_enabled() {
220
221 Node *target = tree->get_selected();
222
223 if (target == nullptr) {
224 get_ok()->set_disabled(true);
225 return;
226 }
227
228 if (!advanced->is_pressed() && target->get_script().is_null()) {
229 get_ok()->set_disabled(true);
230 return;
231 }
232
233 get_ok()->set_disabled(false);
234 }
235
_notification(int p_what)236 void ConnectDialog::_notification(int p_what) {
237
238 if (p_what == NOTIFICATION_ENTER_TREE) {
239 bind_editor->edit(cdbinds);
240 }
241 }
242
_bind_methods()243 void ConnectDialog::_bind_methods() {
244
245 ClassDB::bind_method("_advanced_pressed", &ConnectDialog::_advanced_pressed);
246 ClassDB::bind_method("_cancel", &ConnectDialog::_cancel_pressed);
247 ClassDB::bind_method("_tree_node_selected", &ConnectDialog::_tree_node_selected);
248 ClassDB::bind_method("_add_bind", &ConnectDialog::_add_bind);
249 ClassDB::bind_method("_remove_bind", &ConnectDialog::_remove_bind);
250 ClassDB::bind_method("_update_ok_enabled", &ConnectDialog::_update_ok_enabled);
251
252 ADD_SIGNAL(MethodInfo("connected"));
253 }
254
get_source() const255 Node *ConnectDialog::get_source() const {
256
257 return source;
258 }
259
get_signal_name() const260 StringName ConnectDialog::get_signal_name() const {
261
262 return signal;
263 }
264
get_dst_path() const265 NodePath ConnectDialog::get_dst_path() const {
266
267 return dst_path;
268 }
269
set_dst_node(Node * p_node)270 void ConnectDialog::set_dst_node(Node *p_node) {
271
272 tree->set_selected(p_node);
273 }
274
get_dst_method_name() const275 StringName ConnectDialog::get_dst_method_name() const {
276
277 String txt = dst_method->get_text();
278 if (txt.find("(") != -1)
279 txt = txt.left(txt.find("(")).strip_edges();
280 return txt;
281 }
282
set_dst_method(const StringName & p_method)283 void ConnectDialog::set_dst_method(const StringName &p_method) {
284
285 dst_method->set_text(p_method);
286 }
287
get_binds() const288 Vector<Variant> ConnectDialog::get_binds() const {
289
290 return cdbinds->params;
291 }
292
get_deferred() const293 bool ConnectDialog::get_deferred() const {
294
295 return deferred->is_pressed();
296 }
297
get_oneshot() const298 bool ConnectDialog::get_oneshot() const {
299
300 return oneshot->is_pressed();
301 }
302
303 /*
304 * Returns true if ConnectDialog is being used to edit an existing connection.
305 */
is_editing() const306 bool ConnectDialog::is_editing() const {
307
308 return bEditMode;
309 }
310
311 /*
312 * Initialize ConnectDialog and populate fields with expected data.
313 * If creating a connection from scratch, sensible defaults are used.
314 * If editing an existing connection, previous data is retained.
315 */
init(Connection c,bool bEdit)316 void ConnectDialog::init(Connection c, bool bEdit) {
317
318 set_hide_on_ok(false);
319
320 source = static_cast<Node *>(c.source);
321 signal = c.signal;
322
323 tree->set_selected(NULL);
324 tree->set_marked(source, true);
325
326 if (c.target) {
327 set_dst_node(static_cast<Node *>(c.target));
328 set_dst_method(c.method);
329 }
330
331 _update_ok_enabled();
332
333 bool bDeferred = (c.flags & CONNECT_DEFERRED) == CONNECT_DEFERRED;
334 bool bOneshot = (c.flags & CONNECT_ONESHOT) == CONNECT_ONESHOT;
335
336 deferred->set_pressed(bDeferred);
337 oneshot->set_pressed(bOneshot);
338
339 cdbinds->params.clear();
340 cdbinds->params = c.binds;
341 cdbinds->notify_changed();
342
343 bEditMode = bEdit;
344 }
345
popup_dialog(const String & p_for_signal)346 void ConnectDialog::popup_dialog(const String &p_for_signal) {
347
348 from_signal->set_text(p_for_signal);
349 error_label->add_color_override("font_color", get_color("error_color", "Editor"));
350 if (!advanced->is_pressed())
351 error_label->set_visible(!_find_first_script(get_tree()->get_edited_scene_root(), get_tree()->get_edited_scene_root()));
352
353 popup_centered();
354 }
355
_advanced_pressed()356 void ConnectDialog::_advanced_pressed() {
357
358 if (advanced->is_pressed()) {
359 set_custom_minimum_size(Size2(900, 500) * EDSCALE);
360 connect_to_label->set_text(TTR("Connect to Node:"));
361 tree->set_connect_to_script_mode(false);
362
363 vbc_right->show();
364 error_label->hide();
365 } else {
366 set_custom_minimum_size(Size2(600, 500) * EDSCALE);
367 set_size(Size2());
368 connect_to_label->set_text(TTR("Connect to Script:"));
369 tree->set_connect_to_script_mode(true);
370
371 vbc_right->hide();
372 error_label->set_visible(!_find_first_script(get_tree()->get_edited_scene_root(), get_tree()->get_edited_scene_root()));
373 }
374
375 _update_ok_enabled();
376
377 set_position((get_viewport_rect().size - get_custom_minimum_size()) / 2);
378 }
379
ConnectDialog()380 ConnectDialog::ConnectDialog() {
381
382 set_custom_minimum_size(Size2(600, 500) * EDSCALE);
383
384 VBoxContainer *vbc = memnew(VBoxContainer);
385 add_child(vbc);
386
387 HBoxContainer *main_hb = memnew(HBoxContainer);
388 vbc->add_child(main_hb);
389 main_hb->set_v_size_flags(SIZE_EXPAND_FILL);
390
391 VBoxContainer *vbc_left = memnew(VBoxContainer);
392 main_hb->add_child(vbc_left);
393 vbc_left->set_h_size_flags(SIZE_EXPAND_FILL);
394
395 from_signal = memnew(LineEdit);
396 from_signal->set_editable(false);
397 vbc_left->add_margin_child(TTR("From Signal:"), from_signal);
398
399 tree = memnew(SceneTreeEditor(false));
400 tree->set_connecting_signal(true);
401 tree->get_scene_tree()->connect("item_activated", this, "_ok");
402 tree->connect("node_selected", this, "_tree_node_selected");
403 tree->set_connect_to_script_mode(true);
404
405 Node *mc = vbc_left->add_margin_child(TTR("Connect to Script:"), tree, true);
406 connect_to_label = Object::cast_to<Label>(vbc_left->get_child(mc->get_index() - 1));
407
408 error_label = memnew(Label);
409 error_label->set_text(TTR("Scene does not contain any script."));
410 vbc_left->add_child(error_label);
411 error_label->hide();
412
413 vbc_right = memnew(VBoxContainer);
414 main_hb->add_child(vbc_right);
415 vbc_right->set_h_size_flags(SIZE_EXPAND_FILL);
416 vbc_right->hide();
417
418 HBoxContainer *add_bind_hb = memnew(HBoxContainer);
419
420 type_list = memnew(OptionButton);
421 type_list->set_h_size_flags(SIZE_EXPAND_FILL);
422 add_bind_hb->add_child(type_list);
423 type_list->add_item("bool", Variant::BOOL);
424 type_list->add_item("int", Variant::INT);
425 type_list->add_item("real", Variant::REAL);
426 type_list->add_item("String", Variant::STRING);
427 type_list->add_item("Vector2", Variant::VECTOR2);
428 type_list->add_item("Rect2", Variant::RECT2);
429 type_list->add_item("Vector3", Variant::VECTOR3);
430 type_list->add_item("Plane", Variant::PLANE);
431 type_list->add_item("Quat", Variant::QUAT);
432 type_list->add_item("AABB", Variant::AABB);
433 type_list->add_item("Basis", Variant::BASIS);
434 type_list->add_item("Transform", Variant::TRANSFORM);
435 type_list->add_item("Color", Variant::COLOR);
436 type_list->select(0);
437
438 Button *add_bind = memnew(Button);
439 add_bind->set_text(TTR("Add"));
440 add_bind_hb->add_child(add_bind);
441 add_bind->connect("pressed", this, "_add_bind");
442
443 Button *del_bind = memnew(Button);
444 del_bind->set_text(TTR("Remove"));
445 add_bind_hb->add_child(del_bind);
446 del_bind->connect("pressed", this, "_remove_bind");
447
448 vbc_right->add_margin_child(TTR("Add Extra Call Argument:"), add_bind_hb);
449
450 bind_editor = memnew(EditorInspector);
451
452 vbc_right->add_margin_child(TTR("Extra Call Arguments:"), bind_editor, true);
453
454 HBoxContainer *dstm_hb = memnew(HBoxContainer);
455 vbc_left->add_margin_child(TTR("Receiver Method:"), dstm_hb);
456
457 dst_method = memnew(LineEdit);
458 dst_method->set_h_size_flags(SIZE_EXPAND_FILL);
459 dst_method->connect("text_entered", this, "_builtin_text_entered");
460 dstm_hb->add_child(dst_method);
461
462 advanced = memnew(CheckButton);
463 dstm_hb->add_child(advanced);
464 advanced->set_text(TTR("Advanced"));
465 advanced->connect("pressed", this, "_advanced_pressed");
466
467 deferred = memnew(CheckBox);
468 deferred->set_h_size_flags(0);
469 deferred->set_text(TTR("Deferred"));
470 deferred->set_tooltip(TTR("Defers the signal, storing it in a queue and only firing it at idle time."));
471 vbc_right->add_child(deferred);
472
473 oneshot = memnew(CheckBox);
474 oneshot->set_h_size_flags(0);
475 oneshot->set_text(TTR("Oneshot"));
476 oneshot->set_tooltip(TTR("Disconnects the signal after its first emission."));
477 vbc_right->add_child(oneshot);
478
479 set_as_toplevel(true);
480
481 cdbinds = memnew(ConnectDialogBinds);
482
483 error = memnew(AcceptDialog);
484 add_child(error);
485 error->set_title(TTR("Cannot connect signal"));
486 error->get_ok()->set_text(TTR("Close"));
487 get_ok()->set_text(TTR("Connect"));
488 }
489
~ConnectDialog()490 ConnectDialog::~ConnectDialog() {
491
492 memdelete(cdbinds);
493 }
494
495 //////////////////////////////////////////
496
497 // Originally copied and adapted from EditorProperty, try to keep style in sync.
make_custom_tooltip(const String & p_text) const498 Control *ConnectionsDockTree::make_custom_tooltip(const String &p_text) const {
499
500 EditorHelpBit *help_bit = memnew(EditorHelpBit);
501 help_bit->add_style_override("panel", get_stylebox("panel", "TooltipPanel"));
502 help_bit->get_rich_text()->set_fixed_size_to_width(360 * EDSCALE);
503
504 String text = TTR("Signal:") + " [u][b]" + p_text.get_slice("::", 0) + "[/b][/u]";
505 text += p_text.get_slice("::", 1).strip_edges() + "\n";
506 text += p_text.get_slice("::", 2).strip_edges();
507 help_bit->call_deferred("set_text", text); //hack so it uses proper theme once inside scene
508 return help_bit;
509 }
510
511 struct _ConnectionsDockMethodInfoSort {
512
operator ()_ConnectionsDockMethodInfoSort513 _FORCE_INLINE_ bool operator()(const MethodInfo &a, const MethodInfo &b) const {
514 return a.name < b.name;
515 }
516 };
517
518 /*
519 * Post-ConnectDialog callback for creating/editing connections.
520 * Creates or edits connections based on state of the ConnectDialog when "Connect" is pressed.
521 */
_make_or_edit_connection()522 void ConnectionsDock::_make_or_edit_connection() {
523
524 TreeItem *it = tree->get_selected();
525 ERR_FAIL_COND(!it);
526
527 NodePath dst_path = connect_dialog->get_dst_path();
528 Node *target = selectedNode->get_node(dst_path);
529 ERR_FAIL_COND(!target);
530
531 Connection cToMake;
532 cToMake.source = connect_dialog->get_source();
533 cToMake.target = target;
534 cToMake.signal = connect_dialog->get_signal_name();
535 cToMake.method = connect_dialog->get_dst_method_name();
536 cToMake.binds = connect_dialog->get_binds();
537 bool defer = connect_dialog->get_deferred();
538 bool oshot = connect_dialog->get_oneshot();
539 cToMake.flags = CONNECT_PERSIST | (defer ? CONNECT_DEFERRED : 0) | (oshot ? CONNECT_ONESHOT : 0);
540
541 // Conditions to add function: must have a script and must not have the method already
542 // (in the class, the script itself, or inherited).
543 bool add_script_function = false;
544 Ref<Script> script = target->get_script();
545 if (!target->get_script().is_null() && !ClassDB::has_method(target->get_class(), cToMake.method)) {
546 // There is a chance that the method is inherited from another script.
547 bool found_inherited_function = false;
548 Ref<Script> inherited_script = script->get_base_script();
549 while (!inherited_script.is_null()) {
550 int line = inherited_script->get_language()->find_function(cToMake.method, inherited_script->get_source_code());
551 if (line != -1) {
552 found_inherited_function = true;
553 break;
554 }
555
556 inherited_script = inherited_script->get_base_script();
557 }
558
559 add_script_function = !found_inherited_function;
560 }
561 PoolStringArray script_function_args;
562 if (add_script_function) {
563 // Pick up args here before "it" is deleted by update_tree.
564 script_function_args = it->get_metadata(0).operator Dictionary()["args"];
565 for (int i = 0; i < cToMake.binds.size(); i++) {
566 script_function_args.append("extra_arg_" + itos(i) + ":" + Variant::get_type_name(cToMake.binds[i].get_type()));
567 }
568 }
569
570 if (connect_dialog->is_editing()) {
571 _disconnect(*it);
572 _connect(cToMake);
573 } else {
574 _connect(cToMake);
575 }
576
577 // IMPORTANT NOTE: _disconnect and _connect cause an update_tree, which will delete the object "it" is pointing to.
578 it = NULL;
579
580 if (add_script_function) {
581 editor->emit_signal("script_add_function_request", target, cToMake.method, script_function_args);
582 hide();
583 }
584
585 update_tree();
586 }
587
588 /*
589 * Creates single connection w/ undo-redo functionality.
590 */
_connect(Connection cToMake)591 void ConnectionsDock::_connect(Connection cToMake) {
592
593 Node *source = static_cast<Node *>(cToMake.source);
594 Node *target = static_cast<Node *>(cToMake.target);
595
596 if (!source || !target)
597 return;
598
599 undo_redo->create_action(vformat(TTR("Connect '%s' to '%s'"), String(cToMake.signal), String(cToMake.method)));
600
601 undo_redo->add_do_method(source, "connect", cToMake.signal, target, cToMake.method, cToMake.binds, cToMake.flags);
602 undo_redo->add_undo_method(source, "disconnect", cToMake.signal, target, cToMake.method);
603 undo_redo->add_do_method(this, "update_tree");
604 undo_redo->add_undo_method(this, "update_tree");
605 undo_redo->add_do_method(EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor(), "update_tree"); //to force redraw of scene tree
606 undo_redo->add_undo_method(EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor(), "update_tree");
607
608 undo_redo->commit_action();
609 }
610
611 /*
612 * Break single connection w/ undo-redo functionality.
613 */
_disconnect(TreeItem & item)614 void ConnectionsDock::_disconnect(TreeItem &item) {
615
616 Connection c = item.get_metadata(0);
617 ERR_FAIL_COND(c.source != selectedNode); // Shouldn't happen but... Bugcheck.
618
619 undo_redo->create_action(vformat(TTR("Disconnect '%s' from '%s'"), c.signal, c.method));
620
621 undo_redo->add_do_method(selectedNode, "disconnect", c.signal, c.target, c.method);
622 undo_redo->add_undo_method(selectedNode, "connect", c.signal, c.target, c.method, c.binds, c.flags);
623 undo_redo->add_do_method(this, "update_tree");
624 undo_redo->add_undo_method(this, "update_tree");
625 undo_redo->add_do_method(EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor(), "update_tree"); // To force redraw of scene tree.
626 undo_redo->add_undo_method(EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor(), "update_tree");
627
628 undo_redo->commit_action();
629 }
630
631 /*
632 * Break all connections of currently selected signal.
633 * Can undo-redo as a single action.
634 */
_disconnect_all()635 void ConnectionsDock::_disconnect_all() {
636
637 TreeItem *item = tree->get_selected();
638
639 if (!_is_item_signal(*item))
640 return;
641
642 TreeItem *child = item->get_children();
643 String signalName = item->get_metadata(0).operator Dictionary()["name"];
644 undo_redo->create_action(vformat(TTR("Disconnect all from signal: '%s'"), signalName));
645
646 while (child) {
647 Connection c = child->get_metadata(0);
648 undo_redo->add_do_method(selectedNode, "disconnect", c.signal, c.target, c.method);
649 undo_redo->add_undo_method(selectedNode, "connect", c.signal, c.target, c.method, c.binds, c.flags);
650 child = child->get_next();
651 }
652
653 undo_redo->add_do_method(this, "update_tree");
654 undo_redo->add_undo_method(this, "update_tree");
655 undo_redo->add_do_method(EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor(), "update_tree");
656 undo_redo->add_undo_method(EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor(), "update_tree");
657
658 undo_redo->commit_action();
659 }
660
_tree_item_selected()661 void ConnectionsDock::_tree_item_selected() {
662
663 TreeItem *item = tree->get_selected();
664 if (!item) { // Unlikely. Disable button just in case.
665 connect_button->set_text(TTR("Connect..."));
666 connect_button->set_disabled(true);
667 } else if (_is_item_signal(*item)) {
668 connect_button->set_text(TTR("Connect..."));
669 connect_button->set_disabled(false);
670 } else {
671 connect_button->set_text(TTR("Disconnect"));
672 connect_button->set_disabled(false);
673 }
674 }
675
_tree_item_activated()676 void ConnectionsDock::_tree_item_activated() { // "Activation" on double-click.
677
678 TreeItem *item = tree->get_selected();
679
680 if (!item)
681 return;
682
683 if (_is_item_signal(*item)) {
684 _open_connection_dialog(*item);
685 } else {
686 _go_to_script(*item);
687 }
688 }
689
_is_item_signal(TreeItem & item)690 bool ConnectionsDock::_is_item_signal(TreeItem &item) {
691
692 return (item.get_parent() == tree->get_root() || item.get_parent()->get_parent() == tree->get_root());
693 }
694
695 /*
696 * Open connection dialog with TreeItem data to CREATE a brand-new connection.
697 */
_open_connection_dialog(TreeItem & item)698 void ConnectionsDock::_open_connection_dialog(TreeItem &item) {
699
700 String signal = item.get_metadata(0).operator Dictionary()["name"];
701 const String &signalname = signal;
702 String midname = selectedNode->get_name();
703 for (int i = 0; i < midname.length(); i++) { //TODO: Regex filter may be cleaner.
704 CharType c = midname[i];
705 if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')) {
706 if (c == ' ') {
707 // Replace spaces with underlines.
708 c = '_';
709 } else {
710 // Remove any other characters.
711 midname.remove(i);
712 i--;
713 continue;
714 }
715 }
716 midname[i] = c;
717 }
718
719 Node *dst_node = selectedNode->get_owner() ? selectedNode->get_owner() : selectedNode;
720 if (!dst_node || dst_node->get_script().is_null()) {
721 dst_node = _find_first_script(get_tree()->get_edited_scene_root(), get_tree()->get_edited_scene_root());
722 }
723
724 StringName dst_method = "_on_" + midname + "_" + signal;
725
726 Connection c;
727 c.source = selectedNode;
728 c.signal = StringName(signalname);
729 c.target = dst_node;
730 c.method = dst_method;
731 connect_dialog->popup_dialog(signalname);
732 connect_dialog->init(c);
733 connect_dialog->set_title(TTR("Connect a Signal to a Method"));
734 }
735
736 /*
737 * Open connection dialog with Connection data to EDIT an existing connection.
738 */
_open_connection_dialog(Connection cToEdit)739 void ConnectionsDock::_open_connection_dialog(Connection cToEdit) {
740
741 Node *src = static_cast<Node *>(cToEdit.source);
742 Node *dst = static_cast<Node *>(cToEdit.target);
743
744 if (src && dst) {
745 connect_dialog->set_title(TTR("Edit Connection:") + cToEdit.signal);
746 connect_dialog->popup_centered();
747 connect_dialog->init(cToEdit, true);
748 }
749 }
750
751 /*
752 * Open slot method location in script editor.
753 */
_go_to_script(TreeItem & item)754 void ConnectionsDock::_go_to_script(TreeItem &item) {
755
756 if (_is_item_signal(item))
757 return;
758
759 Connection c = item.get_metadata(0);
760 ERR_FAIL_COND(c.source != selectedNode); //shouldn't happen but...bugcheck
761
762 if (!c.target)
763 return;
764
765 Ref<Script> script = c.target->get_script();
766
767 if (script.is_null())
768 return;
769
770 if (script.is_valid() && ScriptEditor::get_singleton()->script_goto_method(script, c.method)) {
771 editor->call("_editor_select", EditorNode::EDITOR_SCRIPT);
772 }
773 }
774
_handle_signal_menu_option(int option)775 void ConnectionsDock::_handle_signal_menu_option(int option) {
776
777 TreeItem *item = tree->get_selected();
778
779 if (!item)
780 return;
781
782 switch (option) {
783 case CONNECT: {
784 _open_connection_dialog(*item);
785 } break;
786 case DISCONNECT_ALL: {
787 StringName signal_name = item->get_metadata(0).operator Dictionary()["name"];
788 disconnect_all_dialog->set_text(vformat(TTR("Are you sure you want to remove all connections from the \"%s\" signal?"), signal_name));
789 disconnect_all_dialog->popup_centered();
790 } break;
791 }
792 }
793
_handle_slot_menu_option(int option)794 void ConnectionsDock::_handle_slot_menu_option(int option) {
795
796 TreeItem *item = tree->get_selected();
797
798 if (!item)
799 return;
800
801 switch (option) {
802 case EDIT: {
803 Connection c = item->get_metadata(0);
804 _open_connection_dialog(c);
805 } break;
806 case GO_TO_SCRIPT: {
807 _go_to_script(*item);
808 } break;
809 case DISCONNECT: {
810 _disconnect(*item);
811 update_tree();
812 } break;
813 }
814 }
815
_rmb_pressed(Vector2 position)816 void ConnectionsDock::_rmb_pressed(Vector2 position) {
817
818 TreeItem *item = tree->get_selected();
819
820 if (!item)
821 return;
822
823 Vector2 global_position = tree->get_global_position() + position;
824
825 if (_is_item_signal(*item)) {
826 signal_menu->set_position(global_position);
827 signal_menu->popup();
828 } else {
829 slot_menu->set_position(global_position);
830 slot_menu->popup();
831 }
832 }
833
_close()834 void ConnectionsDock::_close() {
835
836 hide();
837 }
838
_connect_pressed()839 void ConnectionsDock::_connect_pressed() {
840
841 TreeItem *item = tree->get_selected();
842 if (!item) {
843 connect_button->set_disabled(true);
844 return;
845 }
846
847 if (_is_item_signal(*item)) {
848 _open_connection_dialog(*item);
849 } else {
850 _disconnect(*item);
851 update_tree();
852 }
853 }
854
_notification(int p_what)855 void ConnectionsDock::_notification(int p_what) {
856
857 if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) {
858 update_tree();
859 }
860 }
861
_bind_methods()862 void ConnectionsDock::_bind_methods() {
863
864 ClassDB::bind_method("_make_or_edit_connection", &ConnectionsDock::_make_or_edit_connection);
865 ClassDB::bind_method("_disconnect_all", &ConnectionsDock::_disconnect_all);
866 ClassDB::bind_method("_tree_item_selected", &ConnectionsDock::_tree_item_selected);
867 ClassDB::bind_method("_tree_item_activated", &ConnectionsDock::_tree_item_activated);
868 ClassDB::bind_method("_handle_signal_menu_option", &ConnectionsDock::_handle_signal_menu_option);
869 ClassDB::bind_method("_handle_slot_menu_option", &ConnectionsDock::_handle_slot_menu_option);
870 ClassDB::bind_method("_rmb_pressed", &ConnectionsDock::_rmb_pressed);
871 ClassDB::bind_method("_close", &ConnectionsDock::_close);
872 ClassDB::bind_method("_connect_pressed", &ConnectionsDock::_connect_pressed);
873 ClassDB::bind_method("update_tree", &ConnectionsDock::update_tree);
874 }
875
set_node(Node * p_node)876 void ConnectionsDock::set_node(Node *p_node) {
877
878 selectedNode = p_node;
879 update_tree();
880 }
881
update_tree()882 void ConnectionsDock::update_tree() {
883
884 tree->clear();
885
886 if (!selectedNode)
887 return;
888
889 TreeItem *root = tree->create_item();
890
891 List<MethodInfo> node_signals;
892
893 selectedNode->get_signal_list(&node_signals);
894
895 bool did_script = false;
896 StringName base = selectedNode->get_class();
897
898 while (base) {
899
900 List<MethodInfo> node_signals2;
901 Ref<Texture> icon;
902 String name;
903
904 if (!did_script) {
905
906 Ref<Script> scr = selectedNode->get_script();
907 if (scr.is_valid()) {
908 scr->get_script_signal_list(&node_signals2);
909 if (scr->get_path().is_resource_file())
910 name = scr->get_path().get_file();
911 else
912 name = scr->get_class();
913
914 if (has_icon(scr->get_class(), "EditorIcons")) {
915 icon = get_icon(scr->get_class(), "EditorIcons");
916 }
917 }
918 } else {
919
920 ClassDB::get_signal_list(base, &node_signals2, true);
921 if (has_icon(base, "EditorIcons")) {
922 icon = get_icon(base, "EditorIcons");
923 }
924 name = base;
925 }
926
927 if (!icon.is_valid()) {
928 icon = get_icon("Object", "EditorIcons");
929 }
930
931 TreeItem *pitem = NULL;
932
933 if (node_signals2.size()) {
934 pitem = tree->create_item(root);
935 pitem->set_text(0, name);
936 pitem->set_icon(0, icon);
937 pitem->set_selectable(0, false);
938 pitem->set_editable(0, false);
939 pitem->set_custom_bg_color(0, get_color("prop_subsection", "Editor"));
940 node_signals2.sort();
941 }
942
943 for (List<MethodInfo>::Element *E = node_signals2.front(); E; E = E->next()) {
944
945 MethodInfo &mi = E->get();
946
947 StringName signal_name = mi.name;
948 String signaldesc = "(";
949 PoolStringArray argnames;
950 if (mi.arguments.size()) {
951 for (int i = 0; i < mi.arguments.size(); i++) {
952
953 PropertyInfo &pi = mi.arguments[i];
954
955 if (i > 0)
956 signaldesc += ", ";
957 String tname = "var";
958 if (pi.type == Variant::OBJECT && pi.class_name != StringName()) {
959 tname = pi.class_name.operator String();
960 } else if (pi.type != Variant::NIL) {
961 tname = Variant::get_type_name(pi.type);
962 }
963 signaldesc += (pi.name == "" ? String("arg " + itos(i)) : pi.name) + ": " + tname;
964 argnames.push_back(pi.name + ":" + tname);
965 }
966 }
967 signaldesc += ")";
968
969 TreeItem *item = tree->create_item(pitem);
970 item->set_text(0, String(signal_name) + signaldesc);
971 Dictionary sinfo;
972 sinfo["name"] = signal_name;
973 sinfo["args"] = argnames;
974 item->set_metadata(0, sinfo);
975 item->set_icon(0, get_icon("Signal", "EditorIcons"));
976
977 // Set tooltip with the signal's documentation.
978 {
979 String descr;
980 bool found = false;
981
982 Map<StringName, Map<StringName, String> >::Element *G = descr_cache.find(base);
983 if (G) {
984 Map<StringName, String>::Element *F = G->get().find(signal_name);
985 if (F) {
986 found = true;
987 descr = F->get();
988 }
989 }
990
991 if (!found) {
992 DocData *dd = EditorHelp::get_doc_data();
993 Map<String, DocData::ClassDoc>::Element *F = dd->class_list.find(base);
994 while (F && descr == String()) {
995 for (int i = 0; i < F->get().signals.size(); i++) {
996 if (F->get().signals[i].name == signal_name.operator String()) {
997 descr = F->get().signals[i].description.strip_edges();
998 break;
999 }
1000 }
1001 if (!F->get().inherits.empty()) {
1002 F = dd->class_list.find(F->get().inherits);
1003 } else {
1004 break;
1005 }
1006 }
1007 descr_cache[base][signal_name] = descr;
1008 }
1009
1010 // "::" separators used in make_custom_tooltip for formatting.
1011 item->set_tooltip(0, String(signal_name) + "::" + signaldesc + "::" + descr);
1012 }
1013
1014 // List existing connections
1015 List<Object::Connection> connections;
1016 selectedNode->get_signal_connection_list(signal_name, &connections);
1017
1018 for (List<Object::Connection>::Element *F = connections.front(); F; F = F->next()) {
1019
1020 Object::Connection &c = F->get();
1021 if (!(c.flags & CONNECT_PERSIST))
1022 continue;
1023
1024 Node *target = Object::cast_to<Node>(c.target);
1025 if (!target)
1026 continue;
1027
1028 String path = String(selectedNode->get_path_to(target)) + " :: " + c.method + "()";
1029 if (c.flags & CONNECT_DEFERRED)
1030 path += " (deferred)";
1031 if (c.flags & CONNECT_ONESHOT)
1032 path += " (oneshot)";
1033 if (c.binds.size()) {
1034
1035 path += " binds(";
1036 for (int i = 0; i < c.binds.size(); i++) {
1037
1038 if (i > 0)
1039 path += ", ";
1040 path += c.binds[i].operator String();
1041 }
1042 path += ")";
1043 }
1044
1045 TreeItem *item2 = tree->create_item(item);
1046 item2->set_text(0, path);
1047 item2->set_metadata(0, c);
1048 item2->set_icon(0, get_icon("Slot", "EditorIcons"));
1049 }
1050 }
1051
1052 if (!did_script) {
1053 did_script = true;
1054 } else {
1055 base = ClassDB::get_parent_class(base);
1056 }
1057 }
1058
1059 connect_button->set_text(TTR("Connect..."));
1060 connect_button->set_disabled(true);
1061 }
1062
ConnectionsDock(EditorNode * p_editor)1063 ConnectionsDock::ConnectionsDock(EditorNode *p_editor) {
1064
1065 editor = p_editor;
1066 set_name(TTR("Signals"));
1067
1068 VBoxContainer *vbc = this;
1069
1070 tree = memnew(ConnectionsDockTree);
1071 tree->set_columns(1);
1072 tree->set_select_mode(Tree::SELECT_ROW);
1073 tree->set_hide_root(true);
1074 vbc->add_child(tree);
1075 tree->set_v_size_flags(SIZE_EXPAND_FILL);
1076 tree->set_allow_rmb_select(true);
1077
1078 connect_button = memnew(Button);
1079 HBoxContainer *hb = memnew(HBoxContainer);
1080 vbc->add_child(hb);
1081 hb->add_spacer();
1082 hb->add_child(connect_button);
1083 connect_button->connect("pressed", this, "_connect_pressed");
1084
1085 connect_dialog = memnew(ConnectDialog);
1086 connect_dialog->set_as_toplevel(true);
1087 add_child(connect_dialog);
1088
1089 disconnect_all_dialog = memnew(ConfirmationDialog);
1090 disconnect_all_dialog->set_as_toplevel(true);
1091 add_child(disconnect_all_dialog);
1092 disconnect_all_dialog->connect("confirmed", this, "_disconnect_all");
1093 disconnect_all_dialog->set_text(TTR("Are you sure you want to remove all connections from this signal?"));
1094
1095 signal_menu = memnew(PopupMenu);
1096 add_child(signal_menu);
1097 signal_menu->connect("id_pressed", this, "_handle_signal_menu_option");
1098 signal_menu->add_item(TTR("Connect..."), CONNECT);
1099 signal_menu->add_item(TTR("Disconnect All"), DISCONNECT_ALL);
1100
1101 slot_menu = memnew(PopupMenu);
1102 add_child(slot_menu);
1103 slot_menu->connect("id_pressed", this, "_handle_slot_menu_option");
1104 slot_menu->add_item(TTR("Edit..."), EDIT);
1105 slot_menu->add_item(TTR("Go To Method"), GO_TO_SCRIPT);
1106 slot_menu->add_item(TTR("Disconnect"), DISCONNECT);
1107
1108 connect_dialog->connect("connected", this, "_make_or_edit_connection");
1109 tree->connect("item_selected", this, "_tree_item_selected");
1110 tree->connect("item_activated", this, "_tree_item_activated");
1111 tree->connect("item_rmb_selected", this, "_rmb_pressed");
1112
1113 add_constant_override("separation", 3 * EDSCALE);
1114 }
1115
~ConnectionsDock()1116 ConnectionsDock::~ConnectionsDock() {
1117 }
1118