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