You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-04 12:00:25 +00:00
Merge pull request #109043 from GabCoolDude/resource-signal-viewer
View resource signals in the Connections Dock
This commit is contained in:
@@ -83,17 +83,24 @@ void NodeDock::update_lists() {
|
||||
connections->update_tree();
|
||||
}
|
||||
|
||||
void NodeDock::set_node(Node *p_node) {
|
||||
connections->set_node(p_node);
|
||||
groups->set_current(p_node);
|
||||
void NodeDock::set_object(Object *p_object) {
|
||||
connections->set_object(p_object);
|
||||
groups->set_current(Object::cast_to<Node>(p_object));
|
||||
|
||||
if (p_node) {
|
||||
if (p_object) {
|
||||
if (connections_button->is_pressed()) {
|
||||
connections->show();
|
||||
} else {
|
||||
groups->show();
|
||||
}
|
||||
|
||||
if (Object::cast_to<Resource>(p_object)) {
|
||||
show_connections();
|
||||
groups_button->set_disabled(true);
|
||||
} else {
|
||||
groups_button->set_disabled(false);
|
||||
}
|
||||
|
||||
mode_hb->show();
|
||||
select_a_node->hide();
|
||||
} else {
|
||||
@@ -150,7 +157,7 @@ NodeDock::NodeDock() {
|
||||
|
||||
select_a_node = memnew(Label);
|
||||
select_a_node->set_focus_mode(FOCUS_ACCESSIBILITY);
|
||||
select_a_node->set_text(TTRC("Select a single node to edit its signals and groups."));
|
||||
select_a_node->set_text(TTRC("Select a single node to edit its signals and groups, or select an independent resource to view its signals."));
|
||||
select_a_node->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
|
||||
select_a_node->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
select_a_node->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
|
||||
|
||||
@@ -62,7 +62,7 @@ protected:
|
||||
virtual void load_layout_from_config(const Ref<ConfigFile> &p_layout, const String &p_section) override;
|
||||
|
||||
public:
|
||||
void set_node(Node *p_node);
|
||||
void set_object(Object *p_object);
|
||||
|
||||
void show_groups();
|
||||
void show_connections();
|
||||
|
||||
@@ -1361,7 +1361,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
|
||||
undo_redo->add_undo_method(node, "set_scene_file_path", node->get_scene_file_path());
|
||||
_node_replace_owner(node, node, root);
|
||||
_node_strip_signal_inheritance(node);
|
||||
NodeDock::get_singleton()->set_node(node); // Refresh.
|
||||
NodeDock::get_singleton()->set_object(node); // Refresh.
|
||||
undo_redo->add_do_method(scene_tree, "update_tree");
|
||||
undo_redo->add_undo_method(scene_tree, "update_tree");
|
||||
undo_redo->commit_action();
|
||||
@@ -2861,7 +2861,7 @@ void SceneTreeDock::_delete_confirm(bool p_cut) {
|
||||
editor_history->cleanup_history();
|
||||
InspectorDock::get_singleton()->call("_prepare_history");
|
||||
InspectorDock::get_singleton()->update(nullptr);
|
||||
NodeDock::get_singleton()->set_node(nullptr);
|
||||
NodeDock::get_singleton()->set_object(nullptr);
|
||||
}
|
||||
|
||||
void SceneTreeDock::_update_script_button() {
|
||||
|
||||
@@ -2673,7 +2673,7 @@ void EditorNode::push_node_item(Node *p_node) {
|
||||
void EditorNode::push_item(Object *p_object, const String &p_property, bool p_inspector_only) {
|
||||
if (!p_object) {
|
||||
InspectorDock::get_inspector_singleton()->edit(nullptr);
|
||||
NodeDock::get_singleton()->set_node(nullptr);
|
||||
NodeDock::get_singleton()->set_object(nullptr);
|
||||
SceneTreeDock::get_singleton()->set_selected(nullptr);
|
||||
InspectorDock::get_singleton()->update(nullptr);
|
||||
hide_unused_editors();
|
||||
@@ -2794,7 +2794,7 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update
|
||||
if (!current_obj) {
|
||||
SceneTreeDock::get_singleton()->set_selected(nullptr);
|
||||
InspectorDock::get_inspector_singleton()->edit(nullptr);
|
||||
NodeDock::get_singleton()->set_node(nullptr);
|
||||
NodeDock::get_singleton()->set_object(nullptr);
|
||||
InspectorDock::get_singleton()->update(nullptr);
|
||||
EditorDebuggerNode::get_singleton()->clear_remote_tree_selection();
|
||||
hide_unused_editors();
|
||||
@@ -2828,7 +2828,7 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update
|
||||
if (!p_skip_inspector_update) {
|
||||
InspectorDock::get_inspector_singleton()->edit(current_res);
|
||||
SceneTreeDock::get_singleton()->set_selected(nullptr);
|
||||
NodeDock::get_singleton()->set_node(nullptr);
|
||||
NodeDock::get_singleton()->set_object(current_res);
|
||||
InspectorDock::get_singleton()->update(nullptr);
|
||||
EditorDebuggerNode::get_singleton()->clear_remote_tree_selection();
|
||||
ImportDock::get_singleton()->set_edit_path(current_res->get_path());
|
||||
@@ -2858,7 +2858,7 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update
|
||||
|
||||
InspectorDock::get_inspector_singleton()->edit(current_node);
|
||||
if (current_node->is_inside_tree()) {
|
||||
NodeDock::get_singleton()->set_node(current_node);
|
||||
NodeDock::get_singleton()->set_object(current_node);
|
||||
SceneTreeDock::get_singleton()->set_selected(current_node);
|
||||
SceneTreeDock::get_singleton()->set_selection({ current_node });
|
||||
InspectorDock::get_singleton()->update(current_node);
|
||||
@@ -2870,7 +2870,7 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NodeDock::get_singleton()->set_node(nullptr);
|
||||
NodeDock::get_singleton()->set_object(nullptr);
|
||||
SceneTreeDock::get_singleton()->set_selected(nullptr);
|
||||
InspectorDock::get_singleton()->update(nullptr);
|
||||
}
|
||||
@@ -2918,7 +2918,7 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update
|
||||
}
|
||||
|
||||
InspectorDock::get_inspector_singleton()->edit(current_obj);
|
||||
NodeDock::get_singleton()->set_node(nullptr);
|
||||
NodeDock::get_singleton()->set_object(nullptr);
|
||||
SceneTreeDock::get_singleton()->set_selected(selected_node);
|
||||
SceneTreeDock::get_singleton()->set_selection(multi_nodes);
|
||||
InspectorDock::get_singleton()->update(nullptr);
|
||||
|
||||
@@ -199,7 +199,11 @@ void ConnectDialog::_tree_node_selected() {
|
||||
return;
|
||||
}
|
||||
|
||||
dst_path = source->get_path_to(current);
|
||||
Node *source_node = Object::cast_to<Node>(source);
|
||||
if (source_node) {
|
||||
dst_path = source_node->get_path_to(current);
|
||||
}
|
||||
|
||||
if (!edit_mode) {
|
||||
set_dst_method(generate_method_callback_name(source, signal, current));
|
||||
}
|
||||
@@ -212,7 +216,7 @@ void ConnectDialog::_tree_node_selected() {
|
||||
}
|
||||
|
||||
void ConnectDialog::_focus_currently_connected() {
|
||||
tree->set_selected(source);
|
||||
tree->set_selected(Object::cast_to<Node>(source));
|
||||
}
|
||||
|
||||
void ConnectDialog::_unbind_count_changed(double p_count) {
|
||||
@@ -267,8 +271,9 @@ void ConnectDialog::_remove_bind() {
|
||||
/*
|
||||
* Automatically generates a name for the callback method.
|
||||
*/
|
||||
StringName ConnectDialog::generate_method_callback_name(Node *p_source, const String &p_signal_name, Node *p_target) {
|
||||
String node_name = p_source->get_name();
|
||||
StringName ConnectDialog::generate_method_callback_name(Object *p_source, const String &p_signal_name, Object *p_target) {
|
||||
String node_name = p_source->call("get_name");
|
||||
|
||||
for (int i = 0; i < node_name.length(); i++) { // TODO: Regex filter may be cleaner.
|
||||
char32_t c = node_name[i];
|
||||
if ((i == 0 && !is_unicode_identifier_start(c)) || (i > 0 && !is_unicode_identifier_continue(c))) {
|
||||
@@ -492,7 +497,8 @@ void ConnectDialog::_update_ok_enabled() {
|
||||
}
|
||||
|
||||
void ConnectDialog::_update_warning_label() {
|
||||
Node *dst = source->get_node(dst_path);
|
||||
Node *dst = Object::cast_to<Node>(source)->get_node(dst_path);
|
||||
|
||||
if (dst == nullptr) {
|
||||
warning_label->set_visible(false);
|
||||
return;
|
||||
@@ -537,7 +543,7 @@ void ConnectDialog::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo("connected"));
|
||||
}
|
||||
|
||||
Node *ConnectDialog::get_source() const {
|
||||
Object *ConnectDialog::get_source() const {
|
||||
return source;
|
||||
}
|
||||
|
||||
@@ -693,15 +699,15 @@ void ConnectDialog::shortcut_input(const Ref<InputEvent> &p_event) {
|
||||
void ConnectDialog::init(const ConnectionData &p_cd, const PackedStringArray &p_signal_args, bool p_edit) {
|
||||
set_hide_on_ok(false);
|
||||
|
||||
source = static_cast<Node *>(p_cd.source);
|
||||
source = p_cd.source;
|
||||
signal = p_cd.signal;
|
||||
signal_args = p_signal_args;
|
||||
|
||||
tree->set_selected(nullptr);
|
||||
tree->set_marked(source);
|
||||
tree->set_marked(Object::cast_to<Node>(source));
|
||||
|
||||
if (p_cd.target) {
|
||||
set_dst_node(static_cast<Node *>(p_cd.target));
|
||||
set_dst_node(Object::cast_to<Node>(p_cd.target));
|
||||
set_dst_method(p_cd.method);
|
||||
}
|
||||
|
||||
@@ -985,7 +991,8 @@ void ConnectionsDock::_filter_changed(const String &p_text) {
|
||||
*/
|
||||
void ConnectionsDock::_make_or_edit_connection() {
|
||||
NodePath dst_path = connect_dialog->get_dst_path();
|
||||
Node *target = selected_node->get_node(dst_path);
|
||||
Node *target = Object::cast_to<Node>(selected_object)->get_node(dst_path);
|
||||
|
||||
ERR_FAIL_NULL(target);
|
||||
|
||||
ConnectDialog::ConnectionData cd;
|
||||
@@ -1078,7 +1085,7 @@ void ConnectionsDock::_make_or_edit_connection() {
|
||||
script_function_args.push_back("extra_arg_" + itos(i) + ": " + Variant::get_type_name(cd.binds[i].get_type()));
|
||||
}
|
||||
|
||||
EditorNode::get_singleton()->emit_signal(SNAME("script_add_function_request"), target, cd.method, script_function_args);
|
||||
EditorNode::get_singleton()->emit_signal(SNAME("script_add_function_request"), cd.target, cd.method, script_function_args);
|
||||
}
|
||||
|
||||
if (connect_dialog->is_editing()) {
|
||||
@@ -1095,7 +1102,7 @@ void ConnectionsDock::_make_or_edit_connection() {
|
||||
* Creates single connection w/ undo-redo functionality.
|
||||
*/
|
||||
void ConnectionsDock::_connect(const ConnectDialog::ConnectionData &p_cd) {
|
||||
Node *source = Object::cast_to<Node>(p_cd.source);
|
||||
Object *source = p_cd.source;
|
||||
Node *target = Object::cast_to<Node>(p_cd.target);
|
||||
|
||||
if (!source || !target) {
|
||||
@@ -1119,14 +1126,14 @@ void ConnectionsDock::_connect(const ConnectDialog::ConnectionData &p_cd) {
|
||||
* Break single connection w/ undo-redo functionality.
|
||||
*/
|
||||
void ConnectionsDock::_disconnect(const ConnectDialog::ConnectionData &p_cd) {
|
||||
ERR_FAIL_COND(p_cd.source != selected_node); // Shouldn't happen but... Bugcheck.
|
||||
ERR_FAIL_COND(p_cd.source != selected_object); // Shouldn't happen but... Bugcheck.
|
||||
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(vformat(TTR("Disconnect '%s' from '%s'"), p_cd.signal, p_cd.method));
|
||||
|
||||
Callable callable = p_cd.get_callable();
|
||||
undo_redo->add_do_method(selected_node, "disconnect", p_cd.signal, callable);
|
||||
undo_redo->add_undo_method(selected_node, "connect", p_cd.signal, callable, p_cd.flags);
|
||||
undo_redo->add_do_method(selected_object, "disconnect", p_cd.signal, callable);
|
||||
undo_redo->add_undo_method(selected_object, "connect", p_cd.signal, callable, p_cd.flags);
|
||||
undo_redo->add_do_method(this, "update_tree");
|
||||
undo_redo->add_undo_method(this, "update_tree");
|
||||
undo_redo->add_do_method(SceneTreeDock::get_singleton()->get_tree_editor(), "update_tree"); // To force redraw of scene tree.
|
||||
@@ -1154,8 +1161,8 @@ void ConnectionsDock::_disconnect_all() {
|
||||
Connection connection = child->get_metadata(0);
|
||||
if (!_is_connection_inherited(connection)) {
|
||||
ConnectDialog::ConnectionData cd = connection;
|
||||
undo_redo->add_do_method(selected_node, "disconnect", cd.signal, cd.get_callable());
|
||||
undo_redo->add_undo_method(selected_node, "connect", cd.signal, cd.get_callable(), cd.flags);
|
||||
undo_redo->add_do_method(selected_object, "disconnect", cd.signal, cd.get_callable());
|
||||
undo_redo->add_undo_method(selected_object, "connect", cd.signal, cd.get_callable(), cd.flags);
|
||||
}
|
||||
child = child->get_next();
|
||||
}
|
||||
@@ -1173,7 +1180,7 @@ void ConnectionsDock::_tree_item_selected() {
|
||||
if (item && _get_item_type(*item) == TREE_ITEM_TYPE_SIGNAL) {
|
||||
connect_button->set_text(TTR("Connect..."));
|
||||
connect_button->set_button_icon(get_editor_theme_icon(SNAME("Instance")));
|
||||
connect_button->set_disabled(false);
|
||||
connect_button->set_disabled(is_editing_resource);
|
||||
} else if (item && _get_item_type(*item) == TREE_ITEM_TYPE_CONNECTION) {
|
||||
connect_button->set_text(TTR("Disconnect"));
|
||||
connect_button->set_button_icon(get_editor_theme_icon(SNAME("Unlinked")));
|
||||
@@ -1220,19 +1227,24 @@ bool ConnectionsDock::_is_connection_inherited(Connection &p_connection) {
|
||||
* Open connection dialog with TreeItem data to CREATE a brand-new connection.
|
||||
*/
|
||||
void ConnectionsDock::_open_connection_dialog(TreeItem &p_item) {
|
||||
if (is_editing_resource) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Dictionary sinfo = p_item.get_metadata(0);
|
||||
const StringName signal_name = sinfo["name"];
|
||||
const PackedStringArray signal_args = sinfo["args"];
|
||||
|
||||
ConnectDialog::ConnectionData cd;
|
||||
|
||||
Node *selected_node = Object::cast_to<Node>(selected_object);
|
||||
Node *dst_node = selected_node->get_owner() ? selected_node->get_owner() : selected_node;
|
||||
if (!dst_node || dst_node->get_script().is_null()) {
|
||||
dst_node = _find_first_script(get_tree()->get_edited_scene_root(), get_tree()->get_edited_scene_root());
|
||||
}
|
||||
|
||||
ConnectDialog::ConnectionData cd;
|
||||
cd.source = selected_node;
|
||||
cd.signal = signal_name;
|
||||
cd.source = selected_object;
|
||||
cd.target = dst_node;
|
||||
cd.signal = signal_name;
|
||||
cd.method = ConnectDialog::generate_method_callback_name(cd.source, signal_name, cd.target);
|
||||
connect_dialog->init(cd, signal_args);
|
||||
connect_dialog->set_title(TTR("Connect a Signal to a Method"));
|
||||
@@ -1249,8 +1261,8 @@ void ConnectionsDock::_open_edit_connection_dialog(TreeItem &p_item) {
|
||||
Connection connection = p_item.get_metadata(0);
|
||||
ConnectDialog::ConnectionData cd = connection;
|
||||
|
||||
Node *src = Object::cast_to<Node>(cd.source);
|
||||
Node *dst = Object::cast_to<Node>(cd.target);
|
||||
Object *src = cd.source;
|
||||
Object *dst = cd.target;
|
||||
|
||||
if (src && dst) {
|
||||
const StringName &signal_name = cd.signal;
|
||||
@@ -1272,7 +1284,7 @@ void ConnectionsDock::_go_to_method(TreeItem &p_item) {
|
||||
|
||||
Connection connection = p_item.get_metadata(0);
|
||||
ConnectDialog::ConnectionData cd = connection;
|
||||
ERR_FAIL_COND(cd.source != selected_node); // Shouldn't happen but... bugcheck.
|
||||
ERR_FAIL_COND(cd.source != selected_object); // Shouldn't happen but... bugcheck.
|
||||
|
||||
if (!cd.target) {
|
||||
return;
|
||||
@@ -1343,6 +1355,7 @@ void ConnectionsDock::_signal_menu_about_to_popup() {
|
||||
}
|
||||
}
|
||||
|
||||
signal_menu->set_item_disabled(signal_menu->get_item_index(SIGNAL_MENU_CONNECT), is_editing_resource);
|
||||
signal_menu->set_item_disabled(signal_menu->get_item_index(SIGNAL_MENU_DISCONNECT_ALL), disable_disconnect_all);
|
||||
signal_menu->set_item_disabled(signal_menu->get_item_index(SIGNAL_MENU_OPEN_DOCS), String(meta["class"]).is_empty());
|
||||
}
|
||||
@@ -1510,8 +1523,9 @@ void ConnectionsDock::_bind_methods() {
|
||||
ClassDB::bind_method("update_tree", &ConnectionsDock::update_tree);
|
||||
}
|
||||
|
||||
void ConnectionsDock::set_node(Node *p_node) {
|
||||
selected_node = p_node;
|
||||
void ConnectionsDock::set_object(Object *p_obj) {
|
||||
selected_object = p_obj;
|
||||
is_editing_resource = (Object::cast_to<Resource>(selected_object) != nullptr);
|
||||
update_tree();
|
||||
}
|
||||
|
||||
@@ -1522,15 +1536,15 @@ void ConnectionsDock::update_tree() {
|
||||
}
|
||||
tree->clear();
|
||||
|
||||
if (!selected_node) {
|
||||
if (!selected_object) {
|
||||
return;
|
||||
}
|
||||
|
||||
TreeItem *root = tree->create_item();
|
||||
DocTools *doc_data = EditorHelp::get_doc_data();
|
||||
EditorData &editor_data = EditorNode::get_editor_data();
|
||||
StringName native_base = selected_node->get_class();
|
||||
Ref<Script> script_base = selected_node->get_script();
|
||||
StringName native_base = selected_object->get_class();
|
||||
Ref<Script> script_base = selected_object->get_script();
|
||||
|
||||
while (native_base != StringName()) {
|
||||
String class_name;
|
||||
@@ -1644,7 +1658,7 @@ void ConnectionsDock::update_tree() {
|
||||
|
||||
// List existing connections.
|
||||
List<Object::Connection> existing_connections;
|
||||
selected_node->get_signal_connection_list(signal_name, &existing_connections);
|
||||
selected_object->get_signal_connection_list(signal_name, &existing_connections);
|
||||
|
||||
for (const Object::Connection &F : existing_connections) {
|
||||
Connection connection = F;
|
||||
@@ -1658,7 +1672,7 @@ void ConnectionsDock::update_tree() {
|
||||
continue;
|
||||
}
|
||||
|
||||
String path = String(selected_node->get_path_to(target)) + " :: " + cd.method + "()";
|
||||
String path = String(Object::cast_to<Node>(selected_object)->get_path_to(target)) + " :: " + cd.method + "()";
|
||||
if (cd.flags & CONNECT_DEFERRED) {
|
||||
path += " (deferred)";
|
||||
}
|
||||
@@ -1695,7 +1709,7 @@ void ConnectionsDock::update_tree() {
|
||||
}
|
||||
}
|
||||
|
||||
connect_button->set_text(TTR("Connect..."));
|
||||
connect_button->set_text(TTRC("Connect..."));
|
||||
connect_button->set_button_icon(get_editor_theme_icon(SNAME("Instance")));
|
||||
connect_button->set_disabled(true);
|
||||
}
|
||||
|
||||
@@ -52,8 +52,8 @@ class ConnectDialog : public ConfirmationDialog {
|
||||
|
||||
public:
|
||||
struct ConnectionData {
|
||||
Node *source = nullptr;
|
||||
Node *target = nullptr;
|
||||
Object *source = nullptr;
|
||||
Object *target = nullptr;
|
||||
StringName signal;
|
||||
StringName method;
|
||||
uint32_t flags = 0;
|
||||
@@ -63,9 +63,9 @@ public:
|
||||
ConnectionData() {}
|
||||
|
||||
ConnectionData(const Connection &p_connection) {
|
||||
source = Object::cast_to<Node>(p_connection.signal.get_object());
|
||||
source = p_connection.signal.get_object();
|
||||
signal = p_connection.signal.get_name();
|
||||
target = Object::cast_to<Node>(p_connection.callable.get_object());
|
||||
target = p_connection.callable.get_object();
|
||||
flags = p_connection.flags;
|
||||
|
||||
Callable base_callable;
|
||||
@@ -112,7 +112,7 @@ private:
|
||||
Label *connect_to_label = nullptr;
|
||||
LineEdit *from_signal = nullptr;
|
||||
LineEdit *filter_nodes = nullptr;
|
||||
Node *source = nullptr;
|
||||
Object *source = nullptr;
|
||||
ConnectionData source_connection_data;
|
||||
StringName signal;
|
||||
PackedStringArray signal_args;
|
||||
@@ -171,8 +171,8 @@ protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static StringName generate_method_callback_name(Node *p_source, const String &p_signal_name, Node *p_target);
|
||||
Node *get_source() const;
|
||||
static StringName generate_method_callback_name(Object *p_source, const String &p_signal_name, Object *p_target);
|
||||
Object *get_source() const;
|
||||
ConnectionData get_source_connection_data() const;
|
||||
StringName get_signal_name() const;
|
||||
PackedStringArray get_signal_args() const;
|
||||
@@ -231,7 +231,7 @@ class ConnectionsDock : public VBoxContainer {
|
||||
SLOT_MENU_DISCONNECT,
|
||||
};
|
||||
|
||||
Node *selected_node = nullptr;
|
||||
Object *selected_object = nullptr;
|
||||
ConnectionsDockTree *tree = nullptr;
|
||||
|
||||
ConfirmationDialog *disconnect_all_dialog = nullptr;
|
||||
@@ -243,6 +243,8 @@ class ConnectionsDock : public VBoxContainer {
|
||||
PopupMenu *slot_menu = nullptr;
|
||||
LineEdit *search_box = nullptr;
|
||||
|
||||
bool is_editing_resource = false;
|
||||
|
||||
void _filter_changed(const String &p_text);
|
||||
|
||||
void _make_or_edit_connection();
|
||||
@@ -274,7 +276,7 @@ protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_node(Node *p_node);
|
||||
void set_object(Object *p_obj);
|
||||
void update_tree();
|
||||
|
||||
ConnectionsDock();
|
||||
|
||||
Reference in New Issue
Block a user