/* * InfiniPaint * Copyright (C) 2025-2026 Yousef Khadadeh * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "WorldUndoManager.hpp" #include "World.hpp" #include "MainProgram.hpp" #include void WorldUndoAction::scale_up(const WorldScalar& scaleAmount) {} WorldUndoAction::~WorldUndoAction() {} WorldUndoManager::WorldUndoManager(World& initWorld): world(initWorld) {} bool WorldUndoManager::can_undo() { return !undoQueue.empty(); } bool WorldUndoManager::can_redo() { return !redoQueue.empty(); } void WorldUndoManager::push(std::unique_ptr undoAction) { std::vector> fullUndoAction; fullUndoAction.emplace_back(std::move(undoAction)); push_undo(std::move(fullUndoAction)); redoQueue.clear(); world.set_has_unsaved_local_changes(true); } void WorldUndoManager::push_on_last(std::unique_ptr undoAction) { if(undoQueue.empty()) push(std::move(undoAction)); else undoQueue.back().emplace_back(std::move(undoAction)); } void WorldUndoManager::push_undo(std::vector> undoAction) { if(undoQueue.size() == UNDO_QUEUE_LIMIT) { if(undoActionSavedAt.has_value()) { if(undoQueue.front().front().get() == undoActionSavedAt.value()) undoActionSavedAt = nullptr; else if(undoActionSavedAt.value() == nullptr) undoActionSavedAt = std::nullopt; } undoQueue.pop_front(); } undoQueue.emplace_back(std::move(undoAction)); } void WorldUndoManager::push_redo(std::vector> undoAction) { redoQueue.emplace_back(std::move(undoAction)); } void WorldUndoManager::undo() { if(undoQueue.empty()) return; world.bMan.refresh_gui_data(); bool undoFail = false; for(auto& u : std::views::reverse(undoQueue.back())) { if(!u->undo(*this)) { undoFail = true; break; } } if(undoFail) { clear(); Logger::get().log(Logger::LogType::INFO, "[WorldUndoManager::undo] Undo failed"); } else { push_redo(std::move(undoQueue.back())); undoQueue.pop_back(); } set_world_has_unsaved_local_changes(); world.main.g.gui.set_to_layout(); } void WorldUndoManager::redo() { if(redoQueue.empty()) return; world.bMan.refresh_gui_data(); bool redoFail = false; for(auto& u : redoQueue.back()) { if(!u->redo(*this)) { redoFail = true; break; } } if(redoFail) { clear(); Logger::get().log(Logger::LogType::INFO, "[WorldUndoManager::redo] Redo failed"); } else { push_undo(std::move(redoQueue.back())); redoQueue.pop_back(); } set_world_has_unsaved_local_changes(); world.main.g.gui.set_to_layout(); } void WorldUndoManager::clear() { redoQueue.clear(); undoQueue.clear(); undoActionSavedAt = std::nullopt; } void WorldUndoManager::scale_up(const WorldScalar& scaleAmount) { for(auto& u : undoQueue) { for(auto& uStep : u) uStep->scale_up(scaleAmount); } for(auto& r : redoQueue) { for(auto& uStep : r) uStep->scale_up(scaleAmount); } } void WorldUndoManager::reassign_netid(const NetworkingObjects::NetObjID& oldNetObjID, const NetworkingObjects::NetObjID& newNetObjID) { auto netIDtoUndoIDIterator = netIDToUndoID.find(oldNetObjID); if(netIDtoUndoIDIterator != netIDToUndoID.end()) { UndoObjectID undoID = netIDtoUndoIDIterator->second; netIDToUndoID.erase(netIDtoUndoIDIterator); netIDToUndoID[newNetObjID] = undoID; undoIDToNetID[undoID] = newNetObjID; } } void WorldUndoManager::remove_by_netid(const NetworkingObjects::NetObjID& netObjID) { auto netIDtoUndoIDIterator = netIDToUndoID.find(netObjID); if(netIDtoUndoIDIterator != netIDToUndoID.end()) { undoIDToNetID.erase(netIDtoUndoIDIterator->second); netIDToUndoID.erase(netIDtoUndoIDIterator); } } void WorldUndoManager::remove_by_undoid(UndoObjectID undoID) { auto undoIDtoNetIDIterator = undoIDToNetID.find(undoID); if(undoIDtoNetIDIterator != undoIDToNetID.end()) { netIDToUndoID.erase(undoIDtoNetIDIterator->second); undoIDToNetID.erase(undoID); } } WorldUndoManager::UndoObjectID WorldUndoManager::get_undoid_from_netid(const NetworkingObjects::NetObjID& netObjID) { auto it = netIDToUndoID.find(netObjID); if(it == netIDToUndoID.end()) { ++lastUndoObjectID; undoIDToNetID[lastUndoObjectID] = netObjID; netIDToUndoID[netObjID] = lastUndoObjectID; return lastUndoObjectID; } return it->second; } void WorldUndoManager::register_new_netid_to_existing_undoid(UndoObjectID existingUndoID, const NetworkingObjects::NetObjID& netObjID) { auto undoIDToNetIDIterator = undoIDToNetID.find(existingUndoID); if(undoIDToNetIDIterator != undoIDToNetID.end()) { netIDToUndoID.erase(undoIDToNetIDIterator->second); undoIDToNetIDIterator->second = netObjID; netIDToUndoID[netObjID] = existingUndoID; } else { netIDToUndoID[netObjID] = existingUndoID; undoIDToNetID[existingUndoID] = netObjID; } } std::optional WorldUndoManager::get_netid_from_undoid(UndoObjectID undoID) { auto it = undoIDToNetID.find(undoID); if(it == undoIDToNetID.end()) return std::nullopt; return it->second; } bool WorldUndoManager::fill_netid_list_from_undoid_list(std::vector& netIDList, const std::vector& undoIDList) { for(UndoObjectID undoID : undoIDList) { auto netIDOpt = get_netid_from_undoid(undoID); if(!netIDOpt.has_value()) return false; netIDList.emplace_back(netIDOpt.value()); } return true; } std::vector WorldUndoManager::get_front_undo_queue_names(unsigned count) { std::vector toRet; for(auto& u : undoQueue | std::views::reverse) { toRet.emplace_back(u.front()->get_name()); if(toRet.size() == count) return toRet; } return toRet; } void WorldUndoManager::set_save_action() { if(undoQueue.empty()) undoActionSavedAt = nullptr; else undoActionSavedAt = undoQueue.back().front().get(); world.set_has_unsaved_local_changes(false); } void WorldUndoManager::set_world_has_unsaved_local_changes() { // Saved action value meanings: // - a pointer to an action: The file was saved when that undo action was the last one in place // - nullptr: The file was saved when undoQueue was empty // - nullopt: The undo action the file was saved at was lost if(!undoActionSavedAt.has_value()) world.set_has_unsaved_local_changes(true); else { if(undoQueue.empty()) world.set_has_unsaved_local_changes(undoActionSavedAt.value() != nullptr); else world.set_has_unsaved_local_changes(undoActionSavedAt.value() != undoQueue.back().front().get()); } }