Path: blob/master/editor/editor_undo_redo_manager.cpp
9821 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);255history.redo_stack.clear();256257if (history.undo_redo->get_action_level() > 0) {258// Nested action.259is_committing = false;260return;261}262263if (!merging) {264history.undo_stack.push_back(pending_action);265}266267if (history.id != GLOBAL_HISTORY) {268// Clear global redo, to avoid unexpected actions when redoing.269History &global = get_or_create_history(GLOBAL_HISTORY);270global.redo_stack.clear();271global.undo_redo->discard_redo();272} else {273// On global actions, clear redo of all scenes instead.274for (KeyValue<int, History> &E : history_map) {275if (E.key == GLOBAL_HISTORY) {276continue;277}278E.value.redo_stack.clear();279E.value.undo_redo->discard_redo();280}281}282283pending_action = Action();284is_committing = false;285emit_signal(SNAME("history_changed"));286}287288bool EditorUndoRedoManager::is_committing_action() const {289return is_committing;290}291292bool EditorUndoRedoManager::undo() {293if (!has_undo()) {294return false;295}296297History *selected_history = _get_newest_undo();298if (selected_history) {299return undo_history(selected_history->id);300}301return false;302}303304bool EditorUndoRedoManager::undo_history(int p_id) {305ERR_FAIL_COND_V(p_id == INVALID_HISTORY, false);306History &history = get_or_create_history(p_id);307308Action action = history.undo_stack.back()->get();309history.undo_stack.pop_back();310history.redo_stack.push_back(action);311312bool success = history.undo_redo->undo();313if (success) {314emit_signal(SNAME("version_changed"));315}316return success;317}318319bool EditorUndoRedoManager::redo() {320if (!has_redo()) {321return false;322}323324int selected_history = INVALID_HISTORY;325double global_timestamp = Math::INF;326327// Pick the history with lowest last action timestamp (either global or current scene).328{329History &history = get_or_create_history(GLOBAL_HISTORY);330if (!history.redo_stack.is_empty()) {331selected_history = history.id;332global_timestamp = history.redo_stack.back()->get().timestamp;333}334}335336{337History &history = get_or_create_history(REMOTE_HISTORY);338if (!history.redo_stack.is_empty() && history.redo_stack.back()->get().timestamp < global_timestamp) {339selected_history = history.id;340global_timestamp = history.redo_stack.back()->get().timestamp;341}342}343344{345History &history = get_or_create_history(EditorNode::get_editor_data().get_current_edited_scene_history_id());346if (!history.redo_stack.is_empty() && history.redo_stack.back()->get().timestamp < global_timestamp) {347selected_history = history.id;348}349}350351if (selected_history != INVALID_HISTORY) {352return redo_history(selected_history);353}354return false;355}356357bool EditorUndoRedoManager::redo_history(int p_id) {358ERR_FAIL_COND_V(p_id == INVALID_HISTORY, false);359History &history = get_or_create_history(p_id);360361Action action = history.redo_stack.back()->get();362history.redo_stack.pop_back();363history.undo_stack.push_back(action);364365bool success = history.undo_redo->redo();366if (success) {367emit_signal(SNAME("version_changed"));368}369return success;370}371372void EditorUndoRedoManager::set_history_as_saved(int p_id) {373History &history = get_or_create_history(p_id);374history.saved_version = history.undo_redo->get_version();375}376377void EditorUndoRedoManager::set_history_as_unsaved(int p_id) {378History &history = get_or_create_history(p_id);379history.saved_version = 0;380}381382bool EditorUndoRedoManager::is_history_unsaved(int p_id) {383History &history = get_or_create_history(p_id);384if (history.saved_version == 0) {385return true;386}387388int version_difference = history.undo_redo->get_version() - history.saved_version;389if (version_difference > 0) {390List<Action>::Element *E = history.undo_stack.back();391for (int i = 0; i < version_difference; i++) {392ERR_FAIL_NULL_V_MSG(E, false, "Inconsistent undo history.");393if (E->get().mark_unsaved) {394return true;395}396E = E->prev();397}398} else if (version_difference < 0) {399List<Action>::Element *E = history.redo_stack.back();400for (int i = 0; i > version_difference; i--) {401ERR_FAIL_NULL_V_MSG(E, false, "Inconsistent redo history.");402if (E->get().mark_unsaved) {403return true;404}405E = E->prev();406}407}408return false;409}410411bool EditorUndoRedoManager::has_undo() {412for (const KeyValue<int, History> &E : history_map) {413if ((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()) {414return true;415}416}417return false;418}419420bool EditorUndoRedoManager::has_redo() {421for (const KeyValue<int, History> &E : history_map) {422if ((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()) {423return true;424}425}426return false;427}428429bool EditorUndoRedoManager::has_history(int p_idx) const {430return history_map.has(p_idx);431}432433void EditorUndoRedoManager::clear_history(int p_idx, bool p_increase_version) {434if (p_idx != INVALID_HISTORY) {435History &history = get_or_create_history(p_idx);436history.undo_redo->clear_history(p_increase_version);437history.undo_stack.clear();438history.redo_stack.clear();439440if (!p_increase_version) {441set_history_as_saved(p_idx);442}443emit_signal(SNAME("history_changed"));444return;445}446447for (const KeyValue<int, History> &E : history_map) {448E.value.undo_redo->clear_history(p_increase_version);449set_history_as_saved(E.key);450}451emit_signal(SNAME("history_changed"));452}453454String EditorUndoRedoManager::get_current_action_name() {455if (has_undo()) {456History *selected_history = _get_newest_undo();457if (selected_history) {458return selected_history->undo_redo->get_current_action_name();459}460}461return "";462}463464int EditorUndoRedoManager::get_current_action_history_id() {465if (has_undo()) {466History *selected_history = _get_newest_undo();467if (selected_history) {468return selected_history->id;469}470}471return INVALID_HISTORY;472}473474void EditorUndoRedoManager::discard_history(int p_idx, bool p_erase_from_map) {475ERR_FAIL_COND(!history_map.has(p_idx));476History &history = history_map[p_idx];477478if (history.undo_redo) {479memdelete(history.undo_redo);480history.undo_redo = nullptr;481}482483if (p_erase_from_map) {484history_map.erase(p_idx);485}486}487488EditorUndoRedoManager::History *EditorUndoRedoManager::_get_newest_undo() {489History *selected_history = nullptr;490double global_timestamp = 0;491492// Pick the history with greatest last action timestamp (either global or current scene).493{494History &history = get_or_create_history(GLOBAL_HISTORY);495if (!history.undo_stack.is_empty()) {496selected_history = &history;497global_timestamp = history.undo_stack.back()->get().timestamp;498}499}500501{502History &history = get_or_create_history(REMOTE_HISTORY);503if (!history.undo_stack.is_empty() && history.undo_stack.back()->get().timestamp > global_timestamp) {504selected_history = &history;505global_timestamp = history.undo_stack.back()->get().timestamp;506}507}508509{510History &history = get_or_create_history(EditorNode::get_editor_data().get_current_edited_scene_history_id());511if (!history.undo_stack.is_empty() && history.undo_stack.back()->get().timestamp > global_timestamp) {512selected_history = &history;513}514}515516return selected_history;517}518519void EditorUndoRedoManager::_bind_methods() {520ClassDB::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));521ClassDB::bind_method(D_METHOD("commit_action", "execute"), &EditorUndoRedoManager::commit_action, DEFVAL(true));522ClassDB::bind_method(D_METHOD("is_committing_action"), &EditorUndoRedoManager::is_committing_action);523ClassDB::bind_method(D_METHOD("force_fixed_history"), &EditorUndoRedoManager::force_fixed_history);524525{526MethodInfo mi;527mi.name = "add_do_method";528mi.arguments.push_back(PropertyInfo(Variant::OBJECT, "object"));529mi.arguments.push_back(PropertyInfo(Variant::STRING_NAME, "method"));530531ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "add_do_method", &EditorUndoRedoManager::_add_do_method, mi, varray(), false);532}533534{535MethodInfo mi;536mi.name = "add_undo_method";537mi.arguments.push_back(PropertyInfo(Variant::OBJECT, "object"));538mi.arguments.push_back(PropertyInfo(Variant::STRING_NAME, "method"));539540ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "add_undo_method", &EditorUndoRedoManager::_add_undo_method, mi, varray(), false);541}542543ClassDB::bind_method(D_METHOD("add_do_property", "object", "property", "value"), &EditorUndoRedoManager::add_do_property);544ClassDB::bind_method(D_METHOD("add_undo_property", "object", "property", "value"), &EditorUndoRedoManager::add_undo_property);545ClassDB::bind_method(D_METHOD("add_do_reference", "object"), &EditorUndoRedoManager::add_do_reference);546ClassDB::bind_method(D_METHOD("add_undo_reference", "object"), &EditorUndoRedoManager::add_undo_reference);547548ClassDB::bind_method(D_METHOD("get_object_history_id", "object"), &EditorUndoRedoManager::get_history_id_for_object);549ClassDB::bind_method(D_METHOD("get_history_undo_redo", "id"), &EditorUndoRedoManager::get_history_undo_redo);550ClassDB::bind_method(D_METHOD("clear_history", "id", "increase_version"), &EditorUndoRedoManager::clear_history, DEFVAL(INVALID_HISTORY), DEFVAL(true));551552ADD_SIGNAL(MethodInfo("history_changed"));553ADD_SIGNAL(MethodInfo("version_changed"));554555BIND_ENUM_CONSTANT(GLOBAL_HISTORY);556BIND_ENUM_CONSTANT(REMOTE_HISTORY);557BIND_ENUM_CONSTANT(INVALID_HISTORY);558}559560EditorUndoRedoManager *EditorUndoRedoManager::get_singleton() {561return singleton;562}563564EditorUndoRedoManager::EditorUndoRedoManager() {565if (!singleton) {566singleton = this;567}568}569570EditorUndoRedoManager::~EditorUndoRedoManager() {571for (const KeyValue<int, History> &E : history_map) {572discard_history(E.key, false);573}574}575576577