Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Yet another node copy-paste PR #34892

Merged
merged 2 commits into from
Feb 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 194 additions & 4 deletions editor/scene_tree_dock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ void SceneTreeDock::_unhandled_key_input(Ref<InputEvent> p_event) {
_tool_selected(TOOL_INSTANCE);
} else if (ED_IS_SHORTCUT("scene_tree/expand_collapse_all", p_event)) {
_tool_selected(TOOL_EXPAND_COLLAPSE);
} else if (ED_IS_SHORTCUT("scene_tree/cut_node", p_event)) {
_tool_selected(TOOL_CUT);
} else if (ED_IS_SHORTCUT("scene_tree/copy_node", p_event)) {
_tool_selected(TOOL_COPY);
} else if (ED_IS_SHORTCUT("scene_tree/paste_node", p_event)) {
_tool_selected(TOOL_PASTE);
} else if (ED_IS_SHORTCUT("scene_tree/change_node_type", p_event)) {
_tool_selected(TOOL_REPLACE);
} else if (ED_IS_SHORTCUT("scene_tree/duplicate", p_event)) {
Expand Down Expand Up @@ -397,6 +403,114 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
tree->ensure_cursor_is_visible();

} break;
case TOOL_CUT:
case TOOL_COPY: {
if (!edited_scene || !_validate_no_foreign()) {
break;
}

List<Node *> selection = editor_selection->get_selected_node_list();
if (selection.size() == 0) {
break;
}

if (!node_clipboard.is_empty()) {
_clear_clipboard();
}
clipboard_source_scene = editor->get_edited_scene()->get_filename();

selection.sort_custom<Node::Comparator>();

for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
Node *node = E->get();
Map<const Node *, Node *> duplimap;
Node *dup = node->duplicate_from_editor(duplimap);

ERR_CONTINUE(!dup);

node_clipboard.push_back(dup);
}

if (p_tool == TOOL_CUT) {
_delete_confirm(true);
}
} break;
case TOOL_PASTE: {
if (node_clipboard.is_empty() || !edited_scene) {
break;
}

bool has_cycle = false;
if (edited_scene->get_filename() != String()) {
for (List<Node *>::Element *E = node_clipboard.front(); E; E = E->next()) {
if (edited_scene->get_filename() == E->get()->get_filename()) {
has_cycle = true;
break;
}
}
}

if (has_cycle) {
current_option = -1;
accept->set_text(TTR("Can't paste root node into the same scene."));
accept->popup_centered();
break;
}

Node *paste_parent = edited_scene;
List<Node *> selection = editor_selection->get_selected_node_list();
if (selection.size() > 0) {
paste_parent = selection.back()->get();
}

Node *owner = paste_parent->get_owner();
if (!owner) {
owner = paste_parent;
}

editor_data->get_undo_redo().create_action(TTR("Paste Node(s)"));
editor_data->get_undo_redo().add_do_method(editor_selection, "clear");

Map<RES, RES> resource_remap;
String target_scene = editor->get_edited_scene()->get_filename();
if (target_scene != clipboard_source_scene) {
if (!clipboard_resource_remap.has(target_scene)) {
Map<RES, RES> remap;
for (List<Node *>::Element *E = node_clipboard.front(); E; E = E->next()) {
_create_remap_for_node(E->get(), remap);
}
clipboard_resource_remap[target_scene] = remap;
}
resource_remap = clipboard_resource_remap[target_scene];
}

for (List<Node *>::Element *E = node_clipboard.front(); E; E = E->next()) {
Node *node = E->get();
Map<const Node *, Node *> duplimap;

Node *dup = node->duplicate_from_editor(duplimap, resource_remap);

ERR_CONTINUE(!dup);

editor_data->get_undo_redo().add_do_method(paste_parent, "add_child", dup);

for (Map<const Node *, Node *>::Element *E2 = duplimap.front(); E2; E2 = E2->next()) {
Node *d = E2->value();
editor_data->get_undo_redo().add_do_method(d, "set_owner", owner);
}
Comment on lines +497 to +500
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This process is different from what is currently done in the TOOL_DUPLICATE case, where it only changes the owner on nodes that were already owned by the one being copied. I'm not sure what cases are covered this way, but should it be the same here?

In general, could duplicate and paste share most of the code since they are similar processes? (apart from the fact paste also handles separate scenes).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, they do share much of the code. Though the most important part is duplicate_from_editor(). I just tried to make a common method that would be called in both cases and well, it didn't work. The method I use in paste is much simplified and adjusted to work in scenarios that duplicate doesn't happen (like, copying scene root or pasting across the scenes).

Is the difference you spotted important? The code never failed me (and I've been using it since it was PRed here), but maybe there's some edge case I didn't predict and it breaks everything (not like it's possible to make the code perfect anyways).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if it's important, and in the duplicate code it was there from the beginning so it's difficult to confirm the reason why it's done this way. I guess time will tell :)

My main point was that it might be safer and cleaner to use a common method, but if the two cases are too different to do that it's fine.


editor_data->get_undo_redo().add_do_method(dup, "set_owner", owner);
editor_data->get_undo_redo().add_do_method(editor_selection, "add_node", dup);
editor_data->get_undo_redo().add_undo_method(paste_parent, "remove_child", dup);
editor_data->get_undo_redo().add_do_reference(dup);

if (node_clipboard.size() == 1) {
editor_data->get_undo_redo().add_do_method(editor, "push_item", dup);
}
}

editor_data->get_undo_redo().commit_action();
} break;
case TOOL_REPLACE: {
if (!profile_allow_editing) {
break;
Expand Down Expand Up @@ -1795,7 +1909,7 @@ void SceneTreeDock::_toggle_editable_children(Node *p_node) {
}
}

void SceneTreeDock::_delete_confirm() {
void SceneTreeDock::_delete_confirm(bool p_cut) {
List<Node *> remove_list = editor_selection->get_selected_node_list();

if (remove_list.is_empty()) {
Expand All @@ -1804,7 +1918,11 @@ void SceneTreeDock::_delete_confirm() {

editor->get_editor_plugins_over()->make_visible(false);

editor_data->get_undo_redo().create_action(TTR("Remove Node(s)"));
if (p_cut) {
editor_data->get_undo_redo().create_action(TTR("Cut Node(s)"));
} else {
editor_data->get_undo_redo().create_action(TTR("Remove Node(s)"));
}

bool entire_scene = false;

Expand Down Expand Up @@ -2444,6 +2562,13 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
}

if (profile_allow_script_editing) {
menu->add_shortcut(ED_GET_SHORTCUT("scene_tree/cut_node"), TOOL_CUT);
menu->add_shortcut(ED_GET_SHORTCUT("scene_tree/copy_node"), TOOL_COPY);
if (selection.size() == 1 && !node_clipboard.is_empty()) {
menu->add_shortcut(ED_GET_SHORTCUT("scene_tree/paste_node"), TOOL_PASTE);
}
menu->add_separator();

bool add_separator = false;

if (full_selection.size() == 1) {
Expand Down Expand Up @@ -2775,6 +2900,62 @@ void SceneTreeDock::_feature_profile_changed() {
_update_script_button();
}

void SceneTreeDock::_clear_clipboard() {
for (List<Node *>::Element *E = node_clipboard.front(); E; E = E->next()) {
memdelete(E->get());
}
node_clipboard.clear();
clipboard_resource_remap.clear();
}

void SceneTreeDock::_create_remap_for_node(Node *p_node, Map<RES, RES> &r_remap) {
List<PropertyInfo> props;
p_node->get_property_list(&props);

for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) {
continue;
}

Variant v = p_node->get(E->get().name);
if (v.is_ref()) {
RES res = v;
if (res.is_valid()) {
if (res->get_path() == "" && !r_remap.has(res)) {
_create_remap_for_resource(res, r_remap);
}
}
}
}

for (int i = 0; i < p_node->get_child_count(); i++) {
_create_remap_for_node(p_node->get_child(i), r_remap);
}
}

void SceneTreeDock::_create_remap_for_resource(RES p_resource, Map<RES, RES> &r_remap) {
r_remap[p_resource] = p_resource->duplicate();

List<PropertyInfo> props;
p_resource->get_property_list(&props);

for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) {
continue;
}

Variant v = p_resource->get(E->get().name);
if (v.is_ref()) {
RES res = v;
if (res.is_valid()) {
if (res->get_path() == "" && !r_remap.has(res)) {
_create_remap_for_resource(res, r_remap);
}
}
}
}
}

void SceneTreeDock::_bind_methods() {
ClassDB::bind_method(D_METHOD("_set_owners"), &SceneTreeDock::_set_owners);
ClassDB::bind_method(D_METHOD("_unhandled_key_input"), &SceneTreeDock::_unhandled_key_input);
Expand Down Expand Up @@ -2806,6 +2987,9 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel
ED_SHORTCUT("scene_tree/add_child_node", TTR("Add Child Node"), KEY_MASK_CMD | KEY_A);
ED_SHORTCUT("scene_tree/instance_scene", TTR("Instance Child Scene"));
ED_SHORTCUT("scene_tree/expand_collapse_all", TTR("Expand/Collapse All"));
ED_SHORTCUT("scene_tree/cut_node", TTR("Cut"), KEY_MASK_CMD | KEY_X);
ED_SHORTCUT("scene_tree/copy_node", TTR("Copy"), KEY_MASK_CMD | KEY_C);
ED_SHORTCUT("scene_tree/paste_node", TTR("Paste"), KEY_MASK_CMD | KEY_V);
ED_SHORTCUT("scene_tree/change_node_type", TTR("Change Type"));
ED_SHORTCUT("scene_tree/attach_script", TTR("Attach Script"));
ED_SHORTCUT("scene_tree/extend_script", TTR("Extend Script"));
Expand All @@ -2818,7 +3002,7 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel
ED_SHORTCUT("scene_tree/make_root", TTR("Make Scene Root"));
ED_SHORTCUT("scene_tree/merge_from_scene", TTR("Merge From Scene"));
ED_SHORTCUT("scene_tree/save_branch_as_scene", TTR("Save Branch as Scene"));
ED_SHORTCUT("scene_tree/copy_node_path", TTR("Copy Node Path"), KEY_MASK_CMD | KEY_C);
ED_SHORTCUT("scene_tree/copy_node_path", TTR("Copy Node Path"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_C);
ED_SHORTCUT("scene_tree/delete_no_confirm", TTR("Delete (No Confirm)"), KEY_MASK_SHIFT | KEY_DELETE);
ED_SHORTCUT("scene_tree/delete", TTR("Delete"), KEY_DELETE);

Expand Down Expand Up @@ -2936,7 +3120,7 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel

delete_dialog = memnew(ConfirmationDialog);
add_child(delete_dialog);
delete_dialog->connect("confirmed", callable_mp(this, &SceneTreeDock::_delete_confirm));
delete_dialog->connect("confirmed", callable_mp(this, &SceneTreeDock::_delete_confirm), varray(false));

editable_instance_remove_dialog = memnew(ConfirmationDialog);
add_child(editable_instance_remove_dialog);
Expand Down Expand Up @@ -2981,3 +3165,9 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel
EDITOR_DEF("interface/editors/derive_script_globals_by_name", true);
EDITOR_DEF("_use_favorites_root_selection", false);
}

SceneTreeDock::~SceneTreeDock() {
if (!node_clipboard.is_empty()) {
_clear_clipboard();
}
}
14 changes: 13 additions & 1 deletion editor/scene_tree_dock.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ class SceneTreeDock : public VBoxContainer {
TOOL_NEW,
TOOL_INSTANCE,
TOOL_EXPAND_COLLAPSE,
TOOL_CUT,
TOOL_COPY,
TOOL_PASTE,
TOOL_RENAME,
TOOL_BATCH_RENAME,
TOOL_REPLACE,
Expand Down Expand Up @@ -126,6 +129,10 @@ class SceneTreeDock : public VBoxContainer {
EditorData *editor_data;
EditorSelection *editor_selection;

List<Node *> node_clipboard;
String clipboard_source_scene;
HashMap<String, Map<RES, RES>> clipboard_resource_remap;

ScriptCreateDialog *script_create_dialog;
AcceptDialog *accept;
ConfirmationDialog *delete_dialog;
Expand Down Expand Up @@ -183,7 +190,7 @@ class SceneTreeDock : public VBoxContainer {
void _script_created(Ref<Script> p_script);
void _script_creation_closed();

void _delete_confirm();
void _delete_confirm(bool p_cut = false);

void _toggle_editable_children_from_selection();
void _toggle_editable_children(Node *p_node);
Expand Down Expand Up @@ -230,6 +237,10 @@ class SceneTreeDock : public VBoxContainer {

void _feature_profile_changed();

void _clear_clipboard();
void _create_remap_for_node(Node *p_node, Map<RES, RES> &r_remap);
void _create_remap_for_resource(RES p_resource, Map<RES, RES> &r_remap);

bool profile_allow_editing;
bool profile_allow_script_editing;

Expand Down Expand Up @@ -267,6 +278,7 @@ class SceneTreeDock : public VBoxContainer {
ScriptCreateDialog *get_script_create_dialog() { return script_create_dialog; }

SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSelection *p_editor_selection, EditorData &p_editor_data);
~SceneTreeDock();
};

#endif // SCENE_TREE_DOCK_H
57 changes: 57 additions & 0 deletions scene/main/node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2146,15 +2146,72 @@ Node *Node::duplicate(int p_flags) const {

#ifdef TOOLS_ENABLED
Node *Node::duplicate_from_editor(Map<const Node *, Node *> &r_duplimap) const {
return duplicate_from_editor(r_duplimap, Map<RES, RES>());
}

Node *Node::duplicate_from_editor(Map<const Node *, Node *> &r_duplimap, const Map<RES, RES> &p_resource_remap) const {
Node *dupe = _duplicate(DUPLICATE_SIGNALS | DUPLICATE_GROUPS | DUPLICATE_SCRIPTS | DUPLICATE_USE_INSTANCING | DUPLICATE_FROM_EDITOR, &r_duplimap);

// This is used by SceneTreeDock's paste functionality. When pasting to foreign scene, resources are duplicated.
if (!p_resource_remap.is_empty()) {
remap_node_resources(dupe, p_resource_remap);
}

// Duplication of signals must happen after all the node descendants have been copied,
// because re-targeting of connections from some descendant to another is not possible
// if the emitter node comes later in tree order than the receiver
_duplicate_signals(this, dupe);

return dupe;
}

void Node::remap_node_resources(Node *p_node, const Map<RES, RES> &p_resource_remap) const {
List<PropertyInfo> props;
p_node->get_property_list(&props);

for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) {
continue;
}

Variant v = p_node->get(E->get().name);
if (v.is_ref()) {
RES res = v;
if (res.is_valid()) {
if (p_resource_remap.has(res)) {
p_node->set(E->get().name, p_resource_remap[res]);
remap_nested_resources(res, p_resource_remap);
}
}
}
}

for (int i = 0; i < p_node->get_child_count(); i++) {
remap_node_resources(p_node->get_child(i), p_resource_remap);
}
}

void Node::remap_nested_resources(RES p_resource, const Map<RES, RES> &p_resource_remap) const {
List<PropertyInfo> props;
p_resource->get_property_list(&props);

for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) {
continue;
}

Variant v = p_resource->get(E->get().name);
if (v.is_ref()) {
RES res = v;
if (res.is_valid()) {
if (p_resource_remap.has(res)) {
p_resource->set(E->get().name, p_resource_remap[res]);
remap_nested_resources(res, p_resource_remap);
}
}
}
}
}
#endif

void Node::_duplicate_and_reown(Node *p_new_parent, const Map<Node *, Node *> &p_reown_map) const {
Expand Down
3 changes: 3 additions & 0 deletions scene/main/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,9 @@ class Node : public Object {
Node *duplicate_and_reown(const Map<Node *, Node *> &p_reown_map) const;
#ifdef TOOLS_ENABLED
Node *duplicate_from_editor(Map<const Node *, Node *> &r_duplimap) const;
Node *duplicate_from_editor(Map<const Node *, Node *> &r_duplimap, const Map<RES, RES> &p_resource_remap) const;
void remap_node_resources(Node *p_node, const Map<RES, RES> &p_resource_remap) const;
void remap_nested_resources(RES p_resource, const Map<RES, RES> &p_resource_remap) const;
#endif

// used by editors, to save what has changed only
Expand Down