242 lines
7.6 KiB
C++
242 lines
7.6 KiB
C++
/*
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "WorldUndoManager.hpp"
|
|
#include "World.hpp"
|
|
#include "MainProgram.hpp"
|
|
#include <Helpers/Logger.hpp>
|
|
|
|
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<WorldUndoAction> undoAction) {
|
|
std::vector<std::unique_ptr<WorldUndoAction>> 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<WorldUndoAction> undoAction) {
|
|
if(undoQueue.empty())
|
|
push(std::move(undoAction));
|
|
else
|
|
undoQueue.back().emplace_back(std::move(undoAction));
|
|
}
|
|
|
|
void WorldUndoManager::push_undo(std::vector<std::unique_ptr<WorldUndoAction>> 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<std::unique_ptr<WorldUndoAction>> 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<NetworkingObjects::NetObjID> 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<NetworkingObjects::NetObjID>& netIDList, const std::vector<UndoObjectID>& 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<std::string> WorldUndoManager::get_front_undo_queue_names(unsigned count) {
|
|
std::vector<std::string> 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());
|
|
}
|
|
}
|