Path: blob/master/editor/editor_undo_redo_manager.cpp
20843 views
/**************************************************************************/1/* editor_undo_redo_manager.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "editor_undo_redo_manager.h"31#include "editor_undo_redo_manager.compat.inc"3233#include "core/io/resource.h"34#include "core/os/os.h"35#include "editor/debugger/editor_debugger_inspector.h"36#include "editor/debugger/editor_debugger_node.h"37#include "editor/editor_log.h"38#include "editor/editor_node.h"39#include "scene/main/node.h"4041EditorUndoRedoManager *EditorUndoRedoManager::singleton = nullptr;4243EditorUndoRedoManager::History &EditorUndoRedoManager::get_or_create_history(int p_idx) {44if (!history_map.has(p_idx)) {45History history;46history.undo_redo = memnew(UndoRedo);47history.id = p_idx;48history_map[p_idx] = history;4950EditorNode::get_singleton()->get_log()->register_undo_redo(history.undo_redo);51EditorDebuggerNode::get_singleton()->register_undo_redo(history.undo_redo);52}53return history_map[p_idx];54}5556UndoRedo *EditorUndoRedoManager::get_history_undo_redo(int p_idx) const {57ERR_FAIL_COND_V(!history_map.has(p_idx), nullptr);58return history_map[p_idx].undo_redo;59}6061int EditorUndoRedoManager::get_history_id_for_object(Object *p_object) const {62int history_id = INVALID_HISTORY;6364if (Object::cast_to<EditorDebuggerRemoteObjects>(p_object)) {65return REMOTE_HISTORY;66}6768if (Node *node = Object::cast_to<Node>(p_object)) {69Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();7071if (edited_scene && (node == edited_scene || edited_scene->is_ancestor_of(node))) {72int idx = EditorNode::get_editor_data().get_current_edited_scene_history_id();73if (idx > 0) {74history_id = idx;75}76}77}7879if (Resource *res = Object::cast_to<Resource>(p_object)) {80if (res->is_built_in()) {81if (res->get_path().is_empty()) {82int idx = EditorNode::get_editor_data().get_current_edited_scene_history_id();83if (idx > 0) {84history_id = idx;85}86} else {87int idx = EditorNode::get_editor_data().get_scene_history_id_from_path(res->get_path().get_slice("::", 0));88if (idx > 0) {89history_id = idx;90}91}92}93}9495if (history_id == INVALID_HISTORY) {96if (pending_action.history_id != INVALID_HISTORY) {97history_id = pending_action.history_id;98} else {99history_id = GLOBAL_HISTORY;100}101}102return history_id;103}104105EditorUndoRedoManager::History &EditorUndoRedoManager::get_history_for_object(Object *p_object) {106int history_id;107if (!forced_history) {108history_id = get_history_id_for_object(p_object);109ERR_FAIL_COND_V_MSG(pending_action.history_id != INVALID_HISTORY && history_id != pending_action.history_id, get_or_create_history(pending_action.history_id), vformat("UndoRedo history mismatch: expected %d, got %d.", pending_action.history_id, history_id));110} else {111history_id = pending_action.history_id;112}113114History &history = get_or_create_history(history_id);115if (pending_action.history_id == INVALID_HISTORY) {116pending_action.history_id = history_id;117history.undo_redo->create_action(pending_action.action_name, pending_action.merge_mode, pending_action.backward_undo_ops);118}119120return history;121}122123void EditorUndoRedoManager::force_fixed_history() {124ERR_FAIL_COND_MSG(pending_action.history_id == INVALID_HISTORY, "The current action has no valid history assigned.");125forced_history = true;126}127128void EditorUndoRedoManager::create_action_for_history(const String &p_name, int p_history_id, UndoRedo::MergeMode p_mode, bool p_backward_undo_ops, bool p_mark_unsaved) {129if (pending_action.history_id != INVALID_HISTORY) {130// Nested action.131p_history_id = pending_action.history_id;132} else {133pending_action.action_name = p_name;134pending_action.timestamp = OS::get_singleton()->get_unix_time();135pending_action.merge_mode = p_mode;136pending_action.backward_undo_ops = p_backward_undo_ops;137pending_action.mark_unsaved = p_mark_unsaved;138}139140if (p_history_id != INVALID_HISTORY) {141pending_action.history_id = p_history_id;142History &history = get_or_create_history(p_history_id);143history.undo_redo->create_action(pending_action.action_name, pending_action.merge_mode, pending_action.backward_undo_ops);144}145}146147void EditorUndoRedoManager::create_action(const String &p_name, UndoRedo::MergeMode p_mode, Object *p_custom_context, bool p_backward_undo_ops, bool p_mark_unsaved) {148create_action_for_history(p_name, INVALID_HISTORY, p_mode, p_backward_undo_ops, p_mark_unsaved);149150if (p_custom_context) {151// This assigns history to pending action.152get_history_for_object(p_custom_context);153}154}155156void EditorUndoRedoManager::add_do_methodp(Object *p_object, const StringName &p_method, const Variant **p_args, int p_argcount) {157UndoRedo *undo_redo = get_history_for_object(p_object).undo_redo;158undo_redo->add_do_method(Callable(p_object, p_method).bindp(p_args, p_argcount));159}160161void EditorUndoRedoManager::add_undo_methodp(Object *p_object, const StringName &p_method, const Variant **p_args, int p_argcount) {162UndoRedo *undo_redo = get_history_for_object(p_object).undo_redo;163undo_redo->add_undo_method(Callable(p_object, p_method).bindp(p_args, p_argcount));164}165166void EditorUndoRedoManager::_add_do_method(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {167if (p_argcount < 2) {168r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;169r_error.expected = 2;170return;171}172173if (p_args[0]->get_type() != Variant::OBJECT) {174r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;175r_error.argument = 0;176r_error.expected = Variant::OBJECT;177return;178}179180if (!p_args[1]->is_string()) {181r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;182r_error.argument = 1;183r_error.expected = Variant::STRING_NAME;184return;185}186187r_error.error = Callable::CallError::CALL_OK;188189Object *object = *p_args[0];190StringName method = *p_args[1];191192add_do_methodp(object, method, p_args + 2, p_argcount - 2);193}194195void EditorUndoRedoManager::_add_undo_method(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {196if (p_argcount < 2) {197r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;198r_error.expected = 2;199return;200}201202if (p_args[0]->get_type() != Variant::OBJECT) {203r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;204r_error.argument = 0;205r_error.expected = Variant::OBJECT;206return;207}208209if (!p_args[1]->is_string()) {210r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;211r_error.argument = 1;212r_error.expected = Variant::STRING_NAME;213return;214}215216r_error.error = Callable::CallError::CALL_OK;217218Object *object = *p_args[0];219StringName method = *p_args[1];220221add_undo_methodp(object, method, p_args + 2, p_argcount - 2);222}223224void EditorUndoRedoManager::add_do_property(Object *p_object, const StringName &p_property, const Variant &p_value) {225UndoRedo *undo_redo = get_history_for_object(p_object).undo_redo;226undo_redo->add_do_property(p_object, p_property, p_value);227}228229void EditorUndoRedoManager::add_undo_property(Object *p_object, const StringName &p_property, const Variant &p_value) {230UndoRedo *undo_redo = get_history_for_object(p_object).undo_redo;231undo_redo->add_undo_property(p_object, p_property, p_value);232}233234void EditorUndoRedoManager::add_do_reference(Object *p_object) {235UndoRedo *undo_redo = get_history_for_object(p_object).undo_redo;236undo_redo->add_do_reference(p_object);237}238239void EditorUndoRedoManager::add_undo_reference(Object *p_object) {240UndoRedo *undo_redo = get_history_for_object(p_object).undo_redo;241undo_redo->add_undo_reference(p_object);242}243244void EditorUndoRedoManager::commit_action(bool p_execute) {245if (pending_action.history_id == INVALID_HISTORY) {246return; // Empty action, do nothing.247}248249forced_history = false;250is_committing = true;251252History &history = get_or_create_history(pending_action.history_id);253bool merging = history.undo_redo->is_merging();254history.undo_redo->commit_action(p_execute);255256if (history.undo_redo->get_action_level() > 0) {257// Nested action.258is_committing = false;259return;260}261262history.redo_stack.clear();263// If you undo history beyond saved version and modify it, the saved version can no longer be restored.264if (history.saved_version >= history.undo_redo->get_version()) {265history.saved_version = UNSAVED_VERSION;266}267268if (!merging) {269history.undo_stack.push_back(pending_action);270}271272if (history.id != GLOBAL_HISTORY) {273// Clear global redo, to avoid unexpected actions when redoing.274History &global = get_or_create_history(GLOBAL_HISTORY);275global.redo_stack.clear();276global.undo_redo->discard_redo();277if (global.saved_version > global.undo_redo->get_version()) {278global.saved_version = UNSAVED_VERSION;279}280} else {281// On global actions, clear redo of all scenes instead.282for (KeyValue<int, History> &E : history_map) {283if (E.key == GLOBAL_HISTORY) {284continue;285}286E.value.redo_stack.clear();287E.value.undo_redo->discard_redo();288if (E.value.saved_version > E.value.undo_redo->get_version()) {289E.value.saved_version = UNSAVED_VERSION;290}291}292}293294pending_action = Action();295is_committing = false;296emit_signal(SNAME("history_changed"));297}298299bool EditorUndoRedoManager::is_committing_action() const {300return is_committing;301}302303bool EditorUndoRedoManager::undo() {304if (!has_undo()) {305return false;306}307308History *selected_history = _get_newest_undo();309if (selected_history) {310return undo_history(selected_history->id);311}312return false;313}314315bool EditorUndoRedoManager::undo_history(int p_id) {316ERR_FAIL_COND_V(p_id == INVALID_HISTORY, false);317History &history = get_or_create_history(p_id);318319Action action = history.undo_stack.back()->get();320history.undo_stack.pop_back();321history.redo_stack.push_back(action);322323bool success = history.undo_redo->undo();324if (success) {325emit_signal(SNAME("version_changed"));326}327return success;328}329330bool EditorUndoRedoManager::redo() {331if (!has_redo()) {332return false;333}334335int selected_history = INVALID_HISTORY;336double global_timestamp = Math::INF;337338// Pick the history with lowest last action timestamp (either global or current scene).339{340History &history = get_or_create_history(GLOBAL_HISTORY);341if (!history.redo_stack.is_empty()) {342selected_history = history.id;343global_timestamp = history.redo_stack.back()->get().timestamp;344}345}346347{348History &history = get_or_create_history(REMOTE_HISTORY);349if (!history.redo_stack.is_empty() && history.redo_stack.back()->get().timestamp < global_timestamp) {350selected_history = history.id;351global_timestamp = history.redo_stack.back()->get().timestamp;352}353}354355{356History &history = get_or_create_history(EditorNode::get_editor_data().get_current_edited_scene_history_id());357if (!history.redo_stack.is_empty() && history.redo_stack.back()->get().timestamp < global_timestamp) {358selected_history = history.id;359}360}361362if (selected_history != INVALID_HISTORY) {363return redo_history(selected_history);364}365return false;366}367368bool EditorUndoRedoManager::redo_history(int p_id) {369ERR_FAIL_COND_V(p_id == INVALID_HISTORY, false);370History &history = get_or_create_history(p_id);371372Action action = history.redo_stack.back()->get();373history.redo_stack.pop_back();374history.undo_stack.push_back(action);375376bool success = history.undo_redo->redo();377if (success) {378emit_signal(SNAME("version_changed"));379}380return success;381}382383void EditorUndoRedoManager::set_history_as_saved(int p_id) {384History &history = get_or_create_history(p_id);385history.saved_version = history.undo_redo->get_version();386}387388void EditorUndoRedoManager::set_history_as_unsaved(int p_id) {389History &history = get_or_create_history(p_id);390history.saved_version = UNSAVED_VERSION;391emit_signal(SNAME("history_changed"));392}393394bool EditorUndoRedoManager::is_history_unsaved(int p_id) {395History &history = get_or_create_history(p_id);396if (history.saved_version == UNSAVED_VERSION) {397return true;398}399400int version_difference = history.undo_redo->get_version() - history.saved_version;401if (version_difference > 0) {402List<Action>::Element *E = history.undo_stack.back();403for (int i = 0; i < version_difference; i++) {404ERR_FAIL_NULL_V_MSG(E, false, "Inconsistent undo history.");405if (E->get().mark_unsaved) {406return true;407}408E = E->prev();409}410} else if (version_difference < 0) {411List<Action>::Element *E = history.redo_stack.back();412for (int i = 0; i > version_difference; i--) {413ERR_FAIL_NULL_V_MSG(E, false, "Inconsistent redo history.");414if (E->get().mark_unsaved) {415return true;416}417E = E->prev();418}419}420return false;421}422423bool EditorUndoRedoManager::has_undo() {424for (const KeyValue<int, History> &E : history_map) {425if ((E.key == GLOBAL_HISTORY || E.key == REMOTE_HISTORY || E.key == EditorNode::get_editor_data().get_current_edited_scene_history_id()) && !E.value.undo_stack.is_empty()) {426return true;427}428}429return false;430}431432bool EditorUndoRedoManager::has_redo() {433for (const KeyValue<int, History> &E : history_map) {434if ((E.key == GLOBAL_HISTORY || E.key == REMOTE_HISTORY || E.key == EditorNode::get_editor_data().get_current_edited_scene_history_id()) && !E.value.redo_stack.is_empty()) {435return true;436}437}438return false;439}440441bool EditorUndoRedoManager::has_history(int p_idx) const {442return history_map.has(p_idx);443}444445void EditorUndoRedoManager::clear_history(int p_idx, bool p_increase_version) {446if (p_idx != INVALID_HISTORY) {447History &history = get_or_create_history(p_idx);448history.undo_redo->clear_history(p_increase_version);449history.undo_stack.clear();450history.redo_stack.clear();451452if (p_increase_version) {453history.saved_version = UNSAVED_VERSION;454} else {455set_history_as_saved(p_idx);456}457emit_signal(SNAME("history_changed"));458return;459}460461for (KeyValue<int, History> &E : history_map) {462if (E.key == REMOTE_HISTORY) {463continue;464}465E.value.undo_redo->clear_history(p_increase_version);466E.value.undo_stack.clear();467E.value.redo_stack.clear();468set_history_as_saved(E.key);469}470emit_signal(SNAME("history_changed"));471}472473String EditorUndoRedoManager::get_current_action_name() {474if (has_undo()) {475History *selected_history = _get_newest_undo();476if (selected_history) {477return selected_history->undo_redo->get_current_action_name();478}479}480return "";481}482483int EditorUndoRedoManager::get_current_action_history_id() {484if (has_undo()) {485History *selected_history = _get_newest_undo();486if (selected_history) {487return selected_history->id;488}489}490return INVALID_HISTORY;491}492493void EditorUndoRedoManager::discard_history(int p_idx, bool p_erase_from_map) {494ERR_FAIL_COND(!history_map.has(p_idx));495History &history = history_map[p_idx];496497if (history.undo_redo) {498memdelete(history.undo_redo);499history.undo_redo = nullptr;500}501502if (p_erase_from_map) {503history_map.erase(p_idx);504}505}506507EditorUndoRedoManager::History *EditorUndoRedoManager::_get_newest_undo() {508History *selected_history = nullptr;509double global_timestamp = 0;510511// Pick the history with greatest last action timestamp (either global or current scene).512{513History &history = get_or_create_history(GLOBAL_HISTORY);514if (!history.undo_stack.is_empty()) {515selected_history = &history;516global_timestamp = history.undo_stack.back()->get().timestamp;517}518}519520{521History &history = get_or_create_history(REMOTE_HISTORY);522if (!history.undo_stack.is_empty() && history.undo_stack.back()->get().timestamp > global_timestamp) {523selected_history = &history;524global_timestamp = history.undo_stack.back()->get().timestamp;525}526}527528{529History &history = get_or_create_history(EditorNode::get_editor_data().get_current_edited_scene_history_id());530if (!history.undo_stack.is_empty() && history.undo_stack.back()->get().timestamp > global_timestamp) {531selected_history = &history;532}533}534535return selected_history;536}537538void EditorUndoRedoManager::_bind_methods() {539ClassDB::bind_method(D_METHOD("create_action", "name", "merge_mode", "custom_context", "backward_undo_ops", "mark_unsaved"), &EditorUndoRedoManager::create_action, DEFVAL(UndoRedo::MERGE_DISABLE), DEFVAL((Object *)nullptr), DEFVAL(false), DEFVAL(true));540ClassDB::bind_method(D_METHOD("commit_action", "execute"), &EditorUndoRedoManager::commit_action, DEFVAL(true));541ClassDB::bind_method(D_METHOD("is_committing_action"), &EditorUndoRedoManager::is_committing_action);542ClassDB::bind_method(D_METHOD("force_fixed_history"), &EditorUndoRedoManager::force_fixed_history);543544{545MethodInfo mi;546mi.name = "add_do_method";547mi.arguments.push_back(PropertyInfo(Variant::OBJECT, "object"));548mi.arguments.push_back(PropertyInfo(Variant::STRING_NAME, "method"));549550ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "add_do_method", &EditorUndoRedoManager::_add_do_method, mi, varray(), false);551}552553{554MethodInfo mi;555mi.name = "add_undo_method";556mi.arguments.push_back(PropertyInfo(Variant::OBJECT, "object"));557mi.arguments.push_back(PropertyInfo(Variant::STRING_NAME, "method"));558559ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "add_undo_method", &EditorUndoRedoManager::_add_undo_method, mi, varray(), false);560}561562ClassDB::bind_method(D_METHOD("add_do_property", "object", "property", "value"), &EditorUndoRedoManager::add_do_property);563ClassDB::bind_method(D_METHOD("add_undo_property", "object", "property", "value"), &EditorUndoRedoManager::add_undo_property);564ClassDB::bind_method(D_METHOD("add_do_reference", "object"), &EditorUndoRedoManager::add_do_reference);565ClassDB::bind_method(D_METHOD("add_undo_reference", "object"), &EditorUndoRedoManager::add_undo_reference);566567ClassDB::bind_method(D_METHOD("get_object_history_id", "object"), &EditorUndoRedoManager::get_history_id_for_object);568ClassDB::bind_method(D_METHOD("get_history_undo_redo", "id"), &EditorUndoRedoManager::get_history_undo_redo);569ClassDB::bind_method(D_METHOD("clear_history", "id", "increase_version"), &EditorUndoRedoManager::clear_history, DEFVAL(INVALID_HISTORY), DEFVAL(true));570571ADD_SIGNAL(MethodInfo("history_changed"));572ADD_SIGNAL(MethodInfo("version_changed"));573574BIND_ENUM_CONSTANT(GLOBAL_HISTORY);575BIND_ENUM_CONSTANT(REMOTE_HISTORY);576BIND_ENUM_CONSTANT(INVALID_HISTORY);577}578579EditorUndoRedoManager *EditorUndoRedoManager::get_singleton() {580return singleton;581}582583EditorUndoRedoManager::EditorUndoRedoManager() {584if (!singleton) {585singleton = this;586}587}588589EditorUndoRedoManager::~EditorUndoRedoManager() {590for (const KeyValue<int, History> &E : history_map) {591discard_history(E.key, false);592}593}594595596