X-Git-Url: http://git.xonotic.org/?a=blobdiff_plain;f=radiant%2Fundo.cpp;h=fb741abdd9db93059102f34a1ced0a595a78d531;hb=ba54332d190b19c00bb8f15ee7d7b7634221646d;hp=d1465af12185b977d1b57e08cddb9cb05c075f10;hpb=80378101101ca1762bbf5638a9e3566893096d8a;p=xonotic%2Fnetradiant.git diff --git a/radiant/undo.cpp b/radiant/undo.cpp index d1465af1..fb741abd 100644 --- a/radiant/undo.cpp +++ b/radiant/undo.cpp @@ -1,973 +1,508 @@ -/* -Copyright (C) 1999-2007 id Software, Inc. and contributors. -For a list of contributors, see the accompanying CONTRIBUTORS file. - -This file is part of GtkRadiant. - -GtkRadiant 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 2 of the License, or -(at your option) any later version. - -GtkRadiant 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 GtkRadiant; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - - -/* - - 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 -*/ - -#include "stdafx.h" - -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) -{ - 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); - } - 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); - 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); - 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) - { - Sys_Printf("Undo_Start: WARNING last undo not finished.\n"); - } - } - - undo = (undo_t *) malloc(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) - { - // Arnout: NOTE - can't do a pointer compare as the brushes get cloned into the undo brushlist, and not just referenced from it - // For entities we have a unique ID, for brushes we have numberID - but brush full clone increases that anyway so it's useless right now. - if (b == brush) return true; - }*/ - // Arnout: function is pointless right now, see above explanation - 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) - { - // Arnout: NOTE - can't do a pointer compare as the entities get cloned into the undo entitylist, and not just referenced from it - //if (e == ent) return true; - if( e->entityId == ent->entityId ) return true; - } - return false; -} - -/* -============= -Undo_Start -============= -*/ -void Undo_Start(char *operation) -{ - // spog - disable undo if undo levels = 0 - if (g_PrefsDlg.m_nUndoLevels == 0) - { -#ifdef DBG_UNDO - Sys_Printf("Undo_Start: undo is disabled.\n"); -#endif - return; - } - - Undo_ClearRedo(); - Undo_GeneralStart(operation); -} - -/* -============= -Undo_AddBrush -============= -*/ -void Undo_AddBrush(brush_t *pBrush) -{ - // spog - disable undo if undo levels = 0 - if (g_PrefsDlg.m_nUndoLevels == 0) - { -#ifdef DBG_UNDO - Sys_Printf("Undo_AddBrush: undo is disabled.\n"); -#endif - return; - } - - if (!g_lastundo) - { - Sys_Printf("Undo_AddBrushList: no last undo.\n"); - return; - } - if (g_lastundo->entitylist.next != &g_lastundo->entitylist) - { - Sys_Printf("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; - //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 -TTimo: some brushes are just there for UI, and the information is somewhere else -for patches it's in the patchMesh_t structure, so when we clone the brush we get that information (brush_t::pPatch) -but: models are stored in pBrush->owner->md3Class, and owner epairs and origin parameters are important - so, we detect models and push the entity in the undo session (as well as it's BBox brush) - same for other items like weapons and ammo etc. -============= -*/ -void Undo_AddBrushList(brush_t *brushlist) -{ - // spog - disable undo if undo levels = 0 - if (g_PrefsDlg.m_nUndoLevels == 0) - { -#ifdef DBG_UNDO - Sys_Printf("Undo_AddBrushList: undo is disabled.\n"); -#endif - return; - } - - brush_t *pBrush; - - if (!g_lastundo) - { - Sys_Printf("Undo_AddBrushList: no last undo.\n"); - return; - } - if (g_lastundo->entitylist.next != &g_lastundo->entitylist) - { - Sys_Printf("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 - //++timo FIXME: when does this happen? - if (Undo_BrushInUndo(g_lastundo, pBrush)) - continue; - // do we need to store this brush's entity in the undo? - // if it's a fixed size entity, the brush that reprents it is not really relevant, it's used for selecting and moving around - // what we want to store for undo is the owner entity, epairs and origin/angle stuff - //++timo FIXME: if the entity is not fixed size I don't know, so I don't do it yet - if (pBrush->owner->eclass->fixedsize == 1) - Undo_AddEntity( pBrush->owner ); - // 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; - Brush_AddToList (pClone, &g_lastundo->brushlist); - // track memory size used by undo - g_undoMemorySize += Brush_MemorySize(pClone); - } -} - -/* -============= -Undo_EndBrush -============= -*/ -void Undo_EndBrush(brush_t *pBrush) -{ - // spog - disable undo if undo levels = 0 - if (g_PrefsDlg.m_nUndoLevels == 0) - { -#ifdef DBG_UNDO - Sys_Printf("Undo_EndBrush: undo is disabled.\n"); -#endif - return; - } - - - if (!g_lastundo) - { - //Sys_Printf("Undo_End: no last undo.\n"); - return; - } - if (g_lastundo->done) - { - //Sys_Printf("Undo_End: last undo already finished.\n"); - return; - } - pBrush->undoId = g_lastundo->id; -} - -/* -============= -Undo_EndBrushList -============= -*/ -void Undo_EndBrushList(brush_t *brushlist) -{ - // spog - disable undo if undo levels = 0 - if (g_PrefsDlg.m_nUndoLevels == 0) - { -#ifdef DBG_UNDO - Sys_Printf("Undo_EndBrushList: undo is disabled.\n"); -#endif - return; - } - - - if (!g_lastundo) - { - //Sys_Printf("Undo_End: no last undo.\n"); - return; - } - if (g_lastundo->done) - { - //Sys_Printf("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) -{ - // spog - disable undo if undo levels = 0 - if (g_PrefsDlg.m_nUndoLevels == 0) - { -#ifdef DBG_UNDO - Sys_Printf("Undo_AddEntity: undo is disabled.\n"); -#endif - return; - } - - - entity_t* pClone; - - if (!g_lastundo) - { - Sys_Printf("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); - //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) -{ - // spog - disable undo if undo levels = 0 - if (g_PrefsDlg.m_nUndoLevels == 0) - { -#ifdef DBG_UNDO - Sys_Printf("Undo_EndEntity: undo is disabled.\n"); -#endif - return; - } - - - if (!g_lastundo) - { -#ifdef _DEBUG - Sys_Printf("Undo_End: no last undo.\n"); -#endif - return; - } - if (g_lastundo->done) - { -#ifdef _DEBUG - Sys_Printf("Undo_End: last undo already finished.\n"); -#endif - return; - } - if (entity == world_entity) - { - //Sys_Printf("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) -{ - // spog - disable undo if undo levels = 0 - if (g_PrefsDlg.m_nUndoLevels == 0) - { -#ifdef DBG_UNDO - Sys_Printf("Undo_End: undo is disabled.\n"); -#endif - return; - } - - - if (!g_lastundo) - { - //Sys_Printf("Undo_End: no last undo.\n"); - return; - } - if (g_lastundo->done) - { - //Sys_Printf("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_Printf("undo size = %d, undo memory = %d\n", g_undoSize, g_undoMemorySize); -} - -/* -============= -Undo_Undo -============= -*/ -void Undo_Undo(boolean bSilent) -{ - // spog - disable undo if undo levels = 0 - if (g_PrefsDlg.m_nUndoLevels == 0) - { - Sys_Printf("Undo_Undo: undo is disabled.\n"); - return; - } - - undo_t *undo, *redo; - brush_t *pBrush, *pNextBrush; - entity_t *pEntity, *pNextEntity, *pUndoEntity; - - if (!g_lastundo) - { - Sys_Printf("Nothing left to undo.\n"); - return; - } - if (!g_lastundo->done) - { - Sys_Printf("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 *) malloc(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; - } - } - - // deselect current sutff - 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) - { - epair_t* tmp = world_entity->epairs; - world_entity->epairs = pEntity->epairs; - pEntity->epairs = tmp; - 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) - { - //Sys_Printf("Owner ID: %i\n",pBrush->ownerId); - g_undoMemorySize -= Brush_MemorySize(pBrush); - Brush_RemoveFromList(pBrush); - Brush_AddToList(pBrush, &active_brushes); - for (pEntity = entities.next; pEntity != NULL && pEntity != &entities; pEntity = pEntity->next) // fixes broken undo on entities - { - //Sys_Printf("Entity ID: %i\n",pEntity->entityId); - 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 - //++timo FIXME: maybe not, maybe we've lost this entity's owner! - if (pEntity == NULL || pEntity == &entities) - { - Entity_LinkBrush(world_entity, pBrush); - } - //build the brush - //Brush_Build(pBrush); - Select_Brush(pBrush); - pBrush->redoId = redo->id; - } - if (!bSilent) - Sys_Printf("%s undone.\n", undo->operation); - // free the undo - g_undoMemorySize -= sizeof(undo_t); - free(undo); - g_undoSize--; - g_undoId--; - if (g_undoId <= 0) g_undoId = 2 * g_undoMaxSize; - // - g_bScreenUpdates = true; - UpdateSurfaceDialog(); - Sys_UpdateWindows(W_ALL); -} - -/* -============= -Undo_Redo -============= -*/ -void Undo_Redo(void) -{ - // spog - disable undo if undo levels = 0 - if (g_PrefsDlg.m_nUndoLevels == 0) - { - Sys_Printf("Undo_Redo: undo is disabled.\n"); - return; - } - - undo_t *redo; - brush_t *pBrush, *pNextBrush; - entity_t *pEntity, *pNextEntity, *pRedoEntity; - - if (!g_lastredo) - { - Sys_Printf("Nothing left to redo.\n"); - return; - } - if (g_lastundo) - { - if (!g_lastundo->done) - { - Sys_Printf("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) - { - epair_t* tmp = world_entity->epairs; - world_entity->epairs = pEntity->epairs; - pEntity->epairs = tmp; - 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) // fixes broken undo on entities - { - 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(); - // - Sys_Printf("%s redone.\n", redo->operation); - // - g_redoId--; - // free the undo - free(redo); - // - g_bScreenUpdates = true; - UpdateSurfaceDialog(); - Sys_UpdateWindows(W_ALL); -} - -/* -============= -Undo_RedoAvailable -============= -*/ -int Undo_RedoAvailable(void) -{ - if (g_lastredo) return true; - return false; -} - -int Undo_GetUndoId(void) -{ - if (g_lastundo) - return g_lastundo->id; - return 0; -} - -/* -============= -Undo_UndoAvailable -============= -*/ -int Undo_UndoAvailable(void) -{ - if (g_lastundo) - { - if (g_lastundo->done) - return true; - } - return false; -} +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant 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 2 of the License, or + (at your option) any later version. + + GtkRadiant 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 GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "undo.h" + +#include "debugging/debugging.h" +#include "warnings.h" + +#include "iundo.h" +#include "preferencesystem.h" +#include "string/string.h" +#include "generic/callback.h" +#include "preferences.h" +#include "stringio.h" + +#include +#include +#include + +#include "timer.h" + +class DebugScopeTimer +{ +Timer m_timer; +const char* m_operation; +public: +DebugScopeTimer( const char* operation ) + : m_operation( operation ){ + m_timer.start(); +} +~DebugScopeTimer(){ + unsigned int elapsed = m_timer.elapsed_msec(); + if ( elapsed > 0 ) { + globalOutputStream() << m_operation << ": " << elapsed << " msec\n"; + } +} +}; + + +class RadiantUndoSystem : public UndoSystem +{ +UINT_CONSTANT( MAX_UNDO_LEVELS, 1024 ); + +class Snapshot +{ +class StateApplicator +{ +public: +Undoable* m_undoable; +private: +UndoMemento* m_data; +public: + +StateApplicator( Undoable* undoable, UndoMemento* data ) + : m_undoable( undoable ), m_data( data ){ +} +void restore(){ + m_undoable->importState( m_data ); +} +void release(){ + m_data->release(); +} +}; + +typedef std::list states_t; +states_t m_states; + +public: +bool empty() const { + return m_states.empty(); +} +std::size_t size() const { + return m_states.size(); +} +void save( Undoable* undoable ){ + m_states.push_front( StateApplicator( undoable, undoable->exportState() ) ); +} +void restore(){ + for ( states_t::iterator i = m_states.begin(); i != m_states.end(); ++i ) + { + ( *i ).restore(); + } +} +void release(){ + for ( states_t::iterator i = m_states.begin(); i != m_states.end(); ++i ) + { + ( *i ).release(); + } +} +}; + +struct Operation +{ + Snapshot m_snapshot; + CopiedString m_command; + + Operation( const char* command ) + : m_command( command ){ + } + ~Operation(){ + m_snapshot.release(); + } +}; + + +class UndoStack +{ +//! Note: using std::list instead of vector/deque, to avoid copying of undos +typedef std::list Operations; + +Operations m_stack; +Operation* m_pending; + +public: +UndoStack() : m_pending( 0 ){ +} +~UndoStack(){ + clear(); +} +bool empty() const { + return m_stack.empty(); +} +std::size_t size() const { + return m_stack.size(); +} +Operation* back(){ + return m_stack.back(); +} +const Operation* back() const { + return m_stack.back(); +} +Operation* front(){ + return m_stack.front(); +} +const Operation* front() const { + return m_stack.front(); +} +void pop_front(){ + delete m_stack.front(); + m_stack.pop_front(); +} +void pop_back(){ + delete m_stack.back(); + m_stack.pop_back(); +} +void clear(){ + if ( !m_stack.empty() ) { + for ( Operations::iterator i = m_stack.begin(); i != m_stack.end(); ++i ) + { + delete *i; + } + m_stack.clear(); + } +} +void start( const char* command ){ + if ( m_pending != 0 ) { + delete m_pending; + } + m_pending = new Operation( command ); +} +bool finish( const char* command ){ + if ( m_pending != 0 ) { + delete m_pending; + m_pending = 0; + return false; + } + else + { + ASSERT_MESSAGE( !m_stack.empty(), "undo stack empty" ); + m_stack.back()->m_command = command; + return true; + } +} +void save( Undoable* undoable ){ + if ( m_pending != 0 ) { + m_stack.push_back( m_pending ); + m_pending = 0; + } + back()->m_snapshot.save( undoable ); +} +}; + +UndoStack m_undo_stack; +UndoStack m_redo_stack; + +class UndoStackFiller : public UndoObserver +{ +UndoStack* m_stack; +public: + +UndoStackFiller() + : m_stack( 0 ){ +} +void save( Undoable* undoable ){ + ASSERT_NOTNULL( undoable ); + + if ( m_stack != 0 ) { + m_stack->save( undoable ); + m_stack = 0; + } +} +void setStack( UndoStack* stack ){ + m_stack = stack; +} +}; + +typedef std::map undoables_t; +undoables_t m_undoables; + +void mark_undoables( UndoStack* stack ){ + for ( undoables_t::iterator i = m_undoables.begin(); i != m_undoables.end(); ++i ) + { + ( *i ).second.setStack( stack ); + } +} + +std::size_t m_undo_levels; + +typedef std::set Trackers; +Trackers m_trackers; +public: +RadiantUndoSystem() + : m_undo_levels( 64 ){ +} +~RadiantUndoSystem(){ + clear(); +} +UndoObserver* observer( Undoable* undoable ){ + ASSERT_NOTNULL( undoable ); + + return &m_undoables[undoable]; +} +void release( Undoable* undoable ){ + ASSERT_NOTNULL( undoable ); + + m_undoables.erase( undoable ); +} +void setLevels( std::size_t levels ){ + if ( levels > MAX_UNDO_LEVELS() ) { + levels = MAX_UNDO_LEVELS(); + } + + while ( m_undo_stack.size() > levels ) + { + m_undo_stack.pop_front(); + } + m_undo_levels = levels; +} +std::size_t getLevels() const { + return m_undo_levels; +} +std::size_t size() const { + return m_undo_stack.size(); +} +void startUndo(){ + m_undo_stack.start( "unnamedCommand" ); + mark_undoables( &m_undo_stack ); +} +bool finishUndo( const char* command ){ + bool changed = m_undo_stack.finish( command ); + mark_undoables( 0 ); + return changed; +} +void startRedo(){ + m_redo_stack.start( "unnamedCommand" ); + mark_undoables( &m_redo_stack ); +} +bool finishRedo( const char* command ){ + bool changed = m_redo_stack.finish( command ); + mark_undoables( 0 ); + return changed; +} +void start(){ + m_redo_stack.clear(); + if ( m_undo_stack.size() == m_undo_levels ) { + m_undo_stack.pop_front(); + } + startUndo(); + trackersBegin(); +} +void finish( const char* command ){ + if ( finishUndo( command ) ) { + globalOutputStream() << command << '\n'; + } +} +void undo(){ + if ( m_undo_stack.empty() ) { + globalOutputStream() << "Undo: no undo available\n"; + } + else + { + Operation* operation = m_undo_stack.back(); + globalOutputStream() << "Undo: " << operation->m_command.c_str() << "\n"; + + startRedo(); + trackersUndo(); + operation->m_snapshot.restore(); + finishRedo( operation->m_command.c_str() ); + m_undo_stack.pop_back(); + } +} +void redo(){ + if ( m_redo_stack.empty() ) { + globalOutputStream() << "Redo: no redo available\n"; + } + else + { + Operation* operation = m_redo_stack.back(); + globalOutputStream() << "Redo: " << operation->m_command.c_str() << "\n"; + + startUndo(); + trackersRedo(); + operation->m_snapshot.restore(); + finishUndo( operation->m_command.c_str() ); + m_redo_stack.pop_back(); + } +} +void clear(){ + mark_undoables( 0 ); + m_undo_stack.clear(); + m_redo_stack.clear(); + trackersClear(); +} +void trackerAttach( UndoTracker& tracker ){ + ASSERT_MESSAGE( m_trackers.find( &tracker ) == m_trackers.end(), "undo tracker already attached" ); + m_trackers.insert( &tracker ); +} +void trackerDetach( UndoTracker& tracker ){ + ASSERT_MESSAGE( m_trackers.find( &tracker ) != m_trackers.end(), "undo tracker cannot be detached" ); + m_trackers.erase( &tracker ); +} +void trackersClear() const { + for ( Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i ) + { + ( *i )->clear(); + } +} +void trackersBegin() const { + for ( Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i ) + { + ( *i )->begin(); + } +} +void trackersUndo() const { + for ( Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i ) + { + ( *i )->undo(); + } +} +void trackersRedo() const { + for ( Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i ) + { + ( *i )->redo(); + } +} +}; + + + +void UndoLevels_importString( RadiantUndoSystem& undo, const char* value ){ + int levels; + PropertyImpl::Import( levels, value ); + undo.setLevels( levels ); +} +typedef ReferenceCaller UndoLevelsImportStringCaller; +void UndoLevels_exportString( const RadiantUndoSystem& undo, const Callback & importer ){ + PropertyImpl::Export( static_cast( undo.getLevels() ), importer ); +} +typedef ConstReferenceCaller &), UndoLevels_exportString> UndoLevelsExportStringCaller; + +#include "generic/callback.h" + +struct UndoLevels { + static void Export(const RadiantUndoSystem &self, const Callback &returnz) { + returnz(static_cast(self.getLevels())); + } + + static void Import(RadiantUndoSystem &self, int value) { + self.setLevels(value); + } +}; + +void Undo_constructPreferences( RadiantUndoSystem& undo, PreferencesPage& page ){ + page.appendSpinner("Undo Queue Size", 64, 0, 1024, make_property(undo)); +} +void Undo_constructPage( RadiantUndoSystem& undo, PreferenceGroup& group ){ + PreferencesPage page( group.createPage( "Undo", "Undo Queue Settings" ) ); + Undo_constructPreferences( undo, page ); +} +void Undo_registerPreferencesPage( RadiantUndoSystem& undo ){ + PreferencesDialog_addSettingsPage( ReferenceCaller( undo ) ); +} + +class UndoSystemDependencies : public GlobalPreferenceSystemModuleRef +{ +}; + +class UndoSystemAPI +{ +RadiantUndoSystem m_undosystem; +public: +typedef UndoSystem Type; +STRING_CONSTANT( Name, "*" ); + +UndoSystemAPI(){ + GlobalPreferenceSystem().registerPreference("UndoLevels", make_property_string(m_undosystem)); + + Undo_registerPreferencesPage( m_undosystem ); +} +UndoSystem* getTable(){ + return &m_undosystem; +} +}; + +#include "modulesystem/singletonmodule.h" +#include "modulesystem/moduleregistry.h" + +typedef SingletonModule UndoSystemModule; +typedef Static StaticUndoSystemModule; +StaticRegisterModule staticRegisterUndoSystem( StaticUndoSystemModule::instance() ); + + + + + + + + + + +class undoable_test : public Undoable +{ +struct state_type : public UndoMemento +{ + state_type() : test_data( 0 ){ + } + state_type( const state_type& other ) : UndoMemento( other ), test_data( other.test_data ){ + } + void release(){ + delete this; + } + + int test_data; +}; +state_type m_state; +UndoObserver* m_observer; +public: +undoable_test() + : m_observer( GlobalUndoSystem().observer( this ) ){ +} +~undoable_test(){ + GlobalUndoSystem().release( this ); +} +UndoMemento* exportState() const { + return new state_type( m_state ); +} +void importState( const UndoMemento* state ){ + ASSERT_NOTNULL( state ); + + m_observer->save( this ); + m_state = *( static_cast( state ) ); +} + +void mutate( unsigned int data ){ + m_observer->save( this ); + m_state.test_data = data; +} +}; + +#if 0 + +class TestUndo +{ +public: +TestUndo(){ + undoable_test test; + GlobalUndoSystem().begin( "bleh" ); + test.mutate( 3 ); + GlobalUndoSystem().begin( "blah" ); + test.mutate( 4 ); + GlobalUndoSystem().undo(); + GlobalUndoSystem().undo(); + GlobalUndoSystem().redo(); + GlobalUndoSystem().redo(); +} +}; + +TestUndo g_TestUndo; + +#endif