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

[3.2] Add node copy-paste #45951

Merged
merged 1 commit into from
Feb 18, 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
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