Skip to content

Commit

Permalink
Merge pull request #45951 from KoBeWi/copy_of_3.2
Browse files Browse the repository at this point in the history
[3.2] Add node copy-paste
  • Loading branch information
akien-mga authored Feb 18, 2021
2 parents 386402e + 637117c commit f30d827
Show file tree
Hide file tree
Showing 4 changed files with 266 additions and 6 deletions.
199 changes: 194 additions & 5 deletions editor/scene_tree_dock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,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 @@ -401,6 +407,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.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.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);
}

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) {
Expand Down Expand Up @@ -1804,16 +1918,19 @@ 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.empty())
return;

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 @@ -2472,6 +2589,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.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 @@ -2812,6 +2936,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() == "" || res->get_path().find("::") > -1) && !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() == "" || res->get_path().find("::") > -1) && !r_remap.has(res)) {
_create_remap_for_resource(res, r_remap);
}
}
}
}
}

void SceneTreeDock::_bind_methods() {

ClassDB::bind_method(D_METHOD("_tool_selected"), &SceneTreeDock::_tool_selected, DEFVAL(false));
Expand Down Expand Up @@ -2875,6 +3055,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 @@ -2887,7 +3070,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 @@ -2997,7 +3180,7 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel

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

editable_instance_remove_dialog = memnew(ConfirmationDialog);
add_child(editable_instance_remove_dialog);
Expand Down Expand Up @@ -3042,3 +3225,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.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 @@ -61,6 +61,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 @@ -129,6 +132,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 @@ -186,7 +193,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 @@ -234,6 +241,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 @@ -271,6 +282,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
56 changes: 56 additions & 0 deletions scene/main/node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2147,16 +2147,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.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 @@ -364,6 +364,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

0 comments on commit f30d827

Please sign in to comment.