/* =========================================================================== Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). Doom 3 Source Code 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. Doom 3 Source Code 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 Doom 3 Source Code. If not, see . In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include "../../idlib/precompiled.h" #pragma hdrstop #include "qe3.h" #include "Radiant.h" /* QERadiant Undo/Redo basic setup: <-g_undolist---------g_lastundo> <---map data---> <-g_lastredo---------g_redolist-> undo/redo on the world_entity is special, only the epair changes are remembered and the world entity never gets deleted. FIXME: maybe reset the Undo system at map load maybe also reset the entityId at map load */ typedef struct undo_s { double time; //time operation was performed int id; //every undo has an unique id int done; //true when undo is build char *operation; //name of the operation brush_t brushlist; //deleted brushes entity_t entitylist; //deleted entities struct undo_s *prev, *next; //next and prev undo in list } undo_t; undo_t *g_undolist; //first undo in the list undo_t *g_lastundo; //last undo in the list undo_t *g_redolist; //first redo in the list undo_t *g_lastredo; //last undo in list int g_undoMaxSize = 64; //maximum number of undos int g_undoSize = 0; //number of undos in the list int g_undoMaxMemorySize = 2*1024*1024; //maximum undo memory (default 2 MB) int g_undoMemorySize = 0; //memory size of undo buffer int g_undoId = 1; //current undo ID (zero is invalid id) int g_redoId = 1; //current redo ID (zero is invalid id) /* ============= Undo_MemorySize ============= */ int Undo_MemorySize(void) { /* int size; undo_t *undo; brush_t *pBrush; entity_t *pEntity; size = 0; for (undo = g_undolist; undo; undo = undo->next) { for (pBrush = undo->brushlist.next ; pBrush != NULL && pBrush != &undo->brushlist ; pBrush = pBrush->next) { size += Brush_MemorySize(pBrush); } for (pEntity = undo->entitylist.next; pEntity != NULL && pEntity != &undo->entitylist; pEntity = pEntity->next) { size += Entity_MemorySize(pEntity); } size += sizeof(undo_t); } return size; */ return g_undoMemorySize; } /* ============= Undo_ClearRedo ============= */ void Undo_ClearRedo(void) { undo_t *redo, *nextredo; brush_t *pBrush, *pNextBrush; entity_t *pEntity, *pNextEntity; for (redo = g_redolist; redo; redo = nextredo) { nextredo = redo->next; for (pBrush = redo->brushlist.next ; pBrush != NULL && pBrush != &redo->brushlist ; pBrush = pNextBrush) { pNextBrush = pBrush->next; Brush_Free(pBrush); } for (pEntity = redo->entitylist.next; pEntity != NULL && pEntity != &redo->entitylist; pEntity = pNextEntity) { pNextEntity = pEntity->next; Entity_Free(pEntity); } Mem_Free(redo); } g_redolist = NULL; g_lastredo = NULL; g_redoId = 1; } /* ============= Undo_Clear Clears the undo buffer. ============= */ void Undo_Clear(void) { undo_t *undo, *nextundo; brush_t *pBrush, *pNextBrush; entity_t *pEntity, *pNextEntity; Undo_ClearRedo(); for (undo = g_undolist; undo; undo = nextundo) { nextundo = undo->next; for (pBrush = undo->brushlist.next ; pBrush != NULL && pBrush != &undo->brushlist ; pBrush = pNextBrush) { pNextBrush = pBrush->next; g_undoMemorySize -= Brush_MemorySize(pBrush); Brush_Free(pBrush); } for (pEntity = undo->entitylist.next; pEntity != NULL && pEntity != &undo->entitylist; pEntity = pNextEntity) { pNextEntity = pEntity->next; g_undoMemorySize -= Entity_MemorySize(pEntity); Entity_Free(pEntity); } g_undoMemorySize -= sizeof(undo_t); Mem_Free(undo); } g_undolist = NULL; g_lastundo = NULL; g_undoSize = 0; g_undoMemorySize = 0; g_undoId = 1; } /* ============= Undo_SetMaxSize ============= */ void Undo_SetMaxSize(int size) { Undo_Clear(); if (size < 1) g_undoMaxSize = 1; else g_undoMaxSize = size; } /* ============= Undo_GetMaxSize ============= */ int Undo_GetMaxSize(void) { return g_undoMaxSize; } /* ============= Undo_SetMaxMemorySize ============= */ void Undo_SetMaxMemorySize(int size) { Undo_Clear(); if (size < 1024) g_undoMaxMemorySize = 1024; else g_undoMaxMemorySize = size; } /* ============= Undo_GetMaxMemorySize ============= */ int Undo_GetMaxMemorySize(void) { return g_undoMaxMemorySize; } /* ============= Undo_FreeFirstUndo ============= */ void Undo_FreeFirstUndo(void) { undo_t *undo; brush_t *pBrush, *pNextBrush; entity_t *pEntity, *pNextEntity; //remove the oldest undo from the undo buffer undo = g_undolist; g_undolist = g_undolist->next; g_undolist->prev = NULL; // for (pBrush = undo->brushlist.next ; pBrush != NULL && pBrush != &undo->brushlist ; pBrush = pNextBrush) { pNextBrush = pBrush->next; g_undoMemorySize -= Brush_MemorySize(pBrush); Brush_Free(pBrush); } for (pEntity = undo->entitylist.next; pEntity != NULL && pEntity != &undo->entitylist; pEntity = pNextEntity) { pNextEntity = pEntity->next; g_undoMemorySize -= Entity_MemorySize(pEntity); Entity_Free(pEntity); } g_undoMemorySize -= sizeof(undo_t); Mem_Free(undo); g_undoSize--; } /* ============= Undo_GeneralStart ============= */ void Undo_GeneralStart(char *operation) { undo_t *undo; brush_t *pBrush; entity_t *pEntity; if (g_lastundo) { if (!g_lastundo->done) { common->Printf("Undo_Start: WARNING last undo not finished.\n"); } } undo = (undo_t *) Mem_ClearedAlloc(sizeof(undo_t)); if (!undo) return; memset(undo, 0, sizeof(undo_t)); undo->brushlist.next = &undo->brushlist; undo->brushlist.prev = &undo->brushlist; undo->entitylist.next = &undo->entitylist; undo->entitylist.prev = &undo->entitylist; if (g_lastundo) g_lastundo->next = undo; else g_undolist = undo; undo->prev = g_lastundo; undo->next = NULL; g_lastundo = undo; undo->time = Sys_DoubleTime(); // if (g_undoId > g_undoMaxSize * 2) g_undoId = 1; if (g_undoId <= 0) g_undoId = 1; undo->id = g_undoId++; undo->done = false; undo->operation = operation; //reset the undo IDs of all brushes using the new ID for (pBrush = active_brushes.next; pBrush != NULL && pBrush != &active_brushes; pBrush = pBrush->next) { if (pBrush->undoId == undo->id) { pBrush->undoId = 0; } } for (pBrush = selected_brushes.next; pBrush != NULL && pBrush != &selected_brushes; pBrush = pBrush->next) { if (pBrush->undoId == undo->id) { pBrush->undoId = 0; } } //reset the undo IDs of all entities using thew new ID for (pEntity = entities.next; pEntity != NULL && pEntity != &entities; pEntity = pEntity->next) { if (pEntity->undoId == undo->id) { pEntity->undoId = 0; } } g_undoMemorySize += sizeof(undo_t); g_undoSize++; //undo buffer is bound to a max if (g_undoSize > g_undoMaxSize) { Undo_FreeFirstUndo(); } } /* ============= Undo_BrushInUndo ============= */ int Undo_BrushInUndo(undo_t *undo, brush_t *brush) { brush_t *b; for (b = undo->brushlist.next; b != &undo->brushlist; b = b->next) { if (b == brush) return true; } return false; } /* ============= Undo_EntityInUndo ============= */ int Undo_EntityInUndo(undo_t *undo, entity_t *ent) { entity_t *e; for (e = undo->entitylist.next; e != &undo->entitylist; e = e->next) { if (e == ent) return true; } return false; } /* ============= Undo_Start ============= */ void Undo_Start(char *operation) { Undo_ClearRedo(); Undo_GeneralStart(operation); } /* ============= Undo_AddBrush ============= */ void Undo_AddBrush(brush_t *pBrush) { if (!g_lastundo) { Sys_Status("Undo_AddBrushList: no last undo.\n"); return; } if (g_lastundo->entitylist.next != &g_lastundo->entitylist) { Sys_Status("Undo_AddBrushList: WARNING adding brushes after entity.\n"); } //if the brush is already in the undo if (Undo_BrushInUndo(g_lastundo, pBrush)) return; //clone the brush brush_t* pClone = Brush_FullClone(pBrush); //save the ID of the owner entity pClone->ownerId = pBrush->owner->entityId; if (pBrush->owner && !(pBrush->owner->eclass->nShowFlags & ECLASS_WORLDSPAWN)) { Undo_AddEntity(pBrush->owner); } //save the old undo ID for previous undos pClone->undoId = pBrush->undoId; Brush_AddToList (pClone, &g_lastundo->brushlist); // g_undoMemorySize += Brush_MemorySize(pClone); } /* ============= Undo_AddBrushList ============= */ void Undo_AddBrushList(brush_t *brushlist) { brush_t *pBrush; if (!g_lastundo) { Sys_Status("Undo_AddBrushList: no last undo.\n"); return; } if (g_lastundo->entitylist.next != &g_lastundo->entitylist) { Sys_Status("Undo_AddBrushList: WARNING adding brushes after entity.\n"); } //copy the brushes to the undo for (pBrush = brushlist->next ; pBrush != NULL && pBrush != brushlist; pBrush=pBrush->next) { //if the brush is already in the undo if (Undo_BrushInUndo(g_lastundo, pBrush)) continue; //clone the brush brush_t* pClone = Brush_FullClone(pBrush); //save the ID of the owner entity pClone->ownerId = pBrush->owner->entityId; //save the old undo ID from previous undos pClone->undoId = pBrush->undoId; if ( pBrush->owner && pBrush->owner != world_entity ) { Undo_AddEntity(pBrush->owner); } Brush_AddToList (pClone, &g_lastundo->brushlist); // g_undoMemorySize += Brush_MemorySize(pClone); } } /* ============= Undo_EndBrush ============= */ void Undo_EndBrush(brush_t *pBrush) { if (!g_lastundo) { //Sys_Status("Undo_End: no last undo.\n"); return; } if (g_lastundo->done) { //Sys_Status("Undo_End: last undo already finished.\n"); return; } pBrush->undoId = g_lastundo->id; } /* ============= Undo_EndBrushList ============= */ void Undo_EndBrushList(brush_t *brushlist) { if (!g_lastundo) { //Sys_Status("Undo_End: no last undo.\n"); return; } if (g_lastundo->done) { //Sys_Status("Undo_End: last undo already finished.\n"); return; } for (brush_t* pBrush = brushlist->next; pBrush != NULL && pBrush != brushlist; pBrush=pBrush->next) { pBrush->undoId = g_lastundo->id; } } /* ============= Undo_AddEntity ============= */ void Undo_AddEntity(entity_t *entity) { entity_t* pClone; if (!g_lastundo) { Sys_Status("Undo_AddEntity: no last undo.\n"); return; } //if the entity is already in the undo if (Undo_EntityInUndo(g_lastundo, entity)) return; //clone the entity pClone = Entity_Clone(entity); //NOTE: Entity_Clone adds the entity to the entity list // so we remove it from that list here Entity_RemoveFromList(pClone); //save the old undo ID for previous undos pClone->undoId = entity->undoId; //save the entity ID (we need a full clone) pClone->entityId = entity->entityId; // Entity_AddToList(pClone, &g_lastundo->entitylist); // g_undoMemorySize += Entity_MemorySize(pClone); } /* ============= Undo_EndEntity ============= */ void Undo_EndEntity(entity_t *entity) { if (!g_lastundo) { //Sys_Status("Undo_End: no last undo.\n"); return; } if (g_lastundo->done) { //Sys_Status("Undo_End: last undo already finished.\n"); return; } if (entity == world_entity) { //Sys_Status("Undo_AddEntity: undo on world entity.\n"); //NOTE: we never delete the world entity when undoing an operation // we only transfer the epairs return; } entity->undoId = g_lastundo->id; } /* ============= Undo_End ============= */ void Undo_End(void) { if (!g_lastundo) { //Sys_Status("Undo_End: no last undo.\n"); return; } if (g_lastundo->done) { //Sys_Status("Undo_End: last undo already finished.\n"); return; } g_lastundo->done = true; //undo memory size is bound to a max while (g_undoMemorySize > g_undoMaxMemorySize) { //always keep one undo if (g_undolist == g_lastundo) break; Undo_FreeFirstUndo(); } // //Sys_Status("undo size = %d, undo memory = %d\n", g_undoSize, g_undoMemorySize); } /* ============= Undo_Undo ============= */ void Undo_Undo(void) { undo_t *undo, *redo; brush_t *pBrush, *pNextBrush; entity_t *pEntity, *pNextEntity, *pUndoEntity; if (!g_lastundo) { Sys_Status("Nothing left to undo.\n"); return; } if (!g_lastundo->done) { Sys_Status("Undo_Undo: WARNING: last undo not yet finished!\n"); } // get the last undo undo = g_lastundo; if (g_lastundo->prev) g_lastundo->prev->next = NULL; else g_undolist = NULL; g_lastundo = g_lastundo->prev; //allocate a new redo redo = (undo_t *) Mem_ClearedAlloc(sizeof(undo_t)); if (!redo) return; memset(redo, 0, sizeof(undo_t)); redo->brushlist.next = &redo->brushlist; redo->brushlist.prev = &redo->brushlist; redo->entitylist.next = &redo->entitylist; redo->entitylist.prev = &redo->entitylist; if (g_lastredo) g_lastredo->next = redo; else g_redolist = redo; redo->prev = g_lastredo; redo->next = NULL; g_lastredo = redo; redo->time = Sys_DoubleTime(); redo->id = g_redoId++; redo->done = true; redo->operation = undo->operation; //reset the redo IDs of all brushes using the new ID for (pBrush = active_brushes.next; pBrush != NULL && pBrush != &active_brushes; pBrush = pBrush->next) { if (pBrush->redoId == redo->id) { pBrush->redoId = 0; } } for (pBrush = selected_brushes.next; pBrush != NULL && pBrush != &selected_brushes; pBrush = pBrush->next) { if (pBrush->redoId == redo->id) { pBrush->redoId = 0; } } //reset the redo IDs of all entities using thew new ID for (pEntity = entities.next; pEntity != NULL && pEntity != &entities; pEntity = pEntity->next) { if (pEntity->redoId == redo->id) { pEntity->redoId = 0; } } // remove current selection Select_Deselect(); // move "created" brushes to the redo for (pBrush = active_brushes.next; pBrush != NULL && pBrush != &active_brushes; pBrush=pNextBrush) { pNextBrush = pBrush->next; if (pBrush->undoId == undo->id) { //Brush_Free(pBrush); //move the brush to the redo Brush_RemoveFromList(pBrush); Brush_AddToList(pBrush, &redo->brushlist); //make sure the ID of the owner is stored pBrush->ownerId = pBrush->owner->entityId; //unlink the brush from the owner entity Entity_UnlinkBrush(pBrush); } } // move "created" entities to the redo for (pEntity = entities.next; pEntity != NULL && pEntity != &entities; pEntity = pNextEntity) { pNextEntity = pEntity->next; if (pEntity->undoId == undo->id) { // check if this entity is in the undo for (pUndoEntity = undo->entitylist.next; pUndoEntity != NULL && pUndoEntity != &undo->entitylist; pUndoEntity = pUndoEntity->next) { // move brushes to the undo entity if (pUndoEntity->entityId == pEntity->entityId) { pUndoEntity->brushes.next = pEntity->brushes.next; pUndoEntity->brushes.prev = pEntity->brushes.prev; pEntity->brushes.next = &pEntity->brushes; pEntity->brushes.prev = &pEntity->brushes; } } // //Entity_Free(pEntity); //move the entity to the redo Entity_RemoveFromList(pEntity); Entity_AddToList(pEntity, &redo->entitylist); } } // add the undo entities back into the entity list for (pEntity = undo->entitylist.next; pEntity != NULL && pEntity != &undo->entitylist; pEntity = undo->entitylist.next) { g_undoMemorySize -= Entity_MemorySize(pEntity); //if this is the world entity if (pEntity->entityId == world_entity->entityId) { //free the epairs of the world entity Entity_FreeEpairs(world_entity); //set back the original epairs world_entity->epairs = pEntity->epairs; //free the world_entity clone that stored the epairs Entity_Free(pEntity); } else { Entity_RemoveFromList(pEntity); Entity_AddToList(pEntity, &entities); pEntity->redoId = redo->id; } } // add the undo brushes back into the selected brushes for (pBrush = undo->brushlist.next; pBrush != NULL && pBrush != &undo->brushlist; pBrush = undo->brushlist.next) { g_undoMemorySize -= Brush_MemorySize(pBrush); Brush_RemoveFromList(pBrush); Brush_AddToList(pBrush, &active_brushes); for (pEntity = entities.next; pEntity != NULL && pEntity != &entities; pEntity = pEntity->next) { if (pEntity->entityId == pBrush->ownerId) { Entity_LinkBrush(pEntity, pBrush); break; } } //if the brush is not linked then it should be linked into the world entity if (pEntity == NULL || pEntity == &entities) { Entity_LinkBrush(world_entity, pBrush); } //build the brush //Brush_Build(pBrush); Select_Brush(pBrush); pBrush->redoId = redo->id; } // common->Printf("%s undone.\n", undo->operation); // free the undo g_undoMemorySize -= sizeof(undo_t); Mem_Free(undo); g_undoSize--; g_undoId--; if (g_undoId <= 0) g_undoId = 2 * g_undoMaxSize; // Sys_BeginWait(); brush_t *b, *next; for (b = active_brushes.next ; b != NULL && b != &active_brushes ; b=next) { next = b->next; Brush_Build( b, true, false, false ); } for (b = selected_brushes.next ; b != NULL && b != &selected_brushes ; b=next) { next = b->next; Brush_Build( b, true, false, false ); } Sys_EndWait(); g_bScreenUpdates = true; Sys_UpdateWindows(W_ALL); } /* ============= Undo_Redo ============= */ void Undo_Redo(void) { undo_t *redo; brush_t *pBrush, *pNextBrush; entity_t *pEntity, *pNextEntity, *pRedoEntity; if (!g_lastredo) { Sys_Status("Nothing left to redo.\n"); return; } if (g_lastundo) { if (!g_lastundo->done) { Sys_Status("WARNING: last undo not finished.\n"); } } // get the last redo redo = g_lastredo; if (g_lastredo->prev) g_lastredo->prev->next = NULL; else g_redolist = NULL; g_lastredo = g_lastredo->prev; // Undo_GeneralStart(redo->operation); // remove current selection Select_Deselect(); // move "created" brushes back to the last undo for (pBrush = active_brushes.next; pBrush != NULL && pBrush != &active_brushes; pBrush = pNextBrush) { pNextBrush = pBrush->next; if (pBrush->redoId == redo->id) { //move the brush to the undo Brush_RemoveFromList(pBrush); Brush_AddToList(pBrush, &g_lastundo->brushlist); g_undoMemorySize += Brush_MemorySize(pBrush); pBrush->ownerId = pBrush->owner->entityId; Entity_UnlinkBrush(pBrush); } } // move "created" entities back to the last undo for (pEntity = entities.next; pEntity != NULL && pEntity != &entities; pEntity = pNextEntity) { pNextEntity = pEntity->next; if (pEntity->redoId == redo->id) { // check if this entity is in the redo for (pRedoEntity = redo->entitylist.next; pRedoEntity != NULL && pRedoEntity != &redo->entitylist; pRedoEntity = pRedoEntity->next) { // move brushes to the redo entity if (pRedoEntity->entityId == pEntity->entityId) { pRedoEntity->brushes.next = pEntity->brushes.next; pRedoEntity->brushes.prev = pEntity->brushes.prev; pEntity->brushes.next = &pEntity->brushes; pEntity->brushes.prev = &pEntity->brushes; } } // //Entity_Free(pEntity); //move the entity to the redo Entity_RemoveFromList(pEntity); Entity_AddToList(pEntity, &g_lastundo->entitylist); g_undoMemorySize += Entity_MemorySize(pEntity); } } // add the undo entities back into the entity list for (pEntity = redo->entitylist.next; pEntity != NULL && pEntity != &redo->entitylist; pEntity = redo->entitylist.next) { //if this is the world entity if (pEntity->entityId == world_entity->entityId) { //free the epairs of the world entity Entity_FreeEpairs(world_entity); //set back the original epairs world_entity->epairs = pEntity->epairs; //free the world_entity clone that stored the epairs Entity_Free(pEntity); } else { Entity_RemoveFromList(pEntity); Entity_AddToList(pEntity, &entities); } } // add the redo brushes back into the selected brushes for (pBrush = redo->brushlist.next; pBrush != NULL && pBrush != &redo->brushlist; pBrush = redo->brushlist.next) { Brush_RemoveFromList(pBrush); Brush_AddToList(pBrush, &active_brushes); for (pEntity = entities.next; pEntity != NULL && pEntity != &entities; pEntity = pEntity->next) { if (pEntity->entityId == pBrush->ownerId) { Entity_LinkBrush(pEntity, pBrush); break; } } //if the brush is not linked then it should be linked into the world entity if (pEntity == NULL || pEntity == &entities) { Entity_LinkBrush(world_entity, pBrush); } //build the brush //Brush_Build(pBrush); Select_Brush(pBrush); } // Undo_End(); // common->Printf("%s redone.\n", redo->operation); // g_redoId--; // free the undo Mem_Free(redo); // g_bScreenUpdates = true; Sys_UpdateWindows(W_ALL); } /* ============= Undo_RedoAvailable ============= */ int Undo_RedoAvailable(void) { if (g_lastredo) return true; return false; } /* ============= Undo_UndoAvailable ============= */ int Undo_UndoAvailable(void) { if (g_lastundo) { if (g_lastundo->done) return true; } return false; }