C Programming Column by Al Stevens Example 1: void Insert(T* pInsertAt, const T& rEvent); void Delete(T* pDeleteAt); void SetUndoContext(C nContext); C GetUndoContext() const; Listing One // --------- undo.h #ifndef UNDO_H #define UNDO_H #include #include namespace DDJCProgrammingColumnUndo { //------------------------------------------------------------------------------ // UndoNode: base class for all undo actions template class UndoData; template class UndoNode { bool m_bTerminator; // undo action stream terminator protected: explicit UndoNode(bool bTerminator) : m_bTerminator(bTerminator) { } public: virtual ~UndoNode() { } virtual void Undo(D& rDoc) = 0; virtual void Redo(D& rDoc) = 0; bool Terminator() const { return m_bTerminator; } void* operator new(size_t sz, UndoData* pData); }; //--------------------------------------------------------------------------- // UndoItem: base template class for all undo actions // D = document class // T = atomic unit of undo action (string, char, etc.) // C = document context information // class D must provide these public functions: // void SetUndoContext(C); // C GetUndoContext() const; // void Delete(T* position); // void Insert(T* position, T datum); // position = where to delete from/insert into // datum = T object to be inserted // classes T and C must support operator= // template class UndoItem : public UndoNode { protected: T* m_pPosition; // document position of undoable action C m_Context; // document view context (cursor, e.g.) at time of action T m_Datum; // data value UndoItem(D& rDoc, T* pPosition, bool bTerminator) : UndoNode(bTerminator), m_pPosition(pPosition) { m_Context = rDoc.GetUndoContext(); } }; //--------------------------------------------------------------------------- // base class for undoing insertion actions // instantiate derived class and add to UndoData stack before performing action template class UndoInsertNode : public UndoItem { public: UndoInsertNode(D& rDoc, T* pPosition, bool bTerminator) : UndoItem(rDoc, pPosition, bTerminator) { } void Undo(D& rDoc) { // --- save datum for undo/redo m_Datum = *m_pPosition; // ---- undo the insertion rDoc.Delete(m_pPosition); rDoc.SetUndoContext(m_Context); } void Redo(D& rDoc) { rDoc.Insert(m_pPosition, m_Datum); rDoc.SetUndoContext(m_Context); } }; //--------------------------------------------------------------------------- // base class for undoing deletion actions // instantiate derived class and add to UndoData stack before performing action template class UndoDeleteNode : public UndoItem { public: UndoDeleteNode(D& rDoc, T* pPosition, bool bTerminator) : UndoItem(rDoc, pPosition, bTerminator) { // --- save datum for undo/redo m_Datum = *m_pPosition; } void Undo(D& rDoc) { rDoc.Insert(m_pPosition, m_Datum); rDoc.SetUndoContext(m_Context); } void Redo(D& rDoc) { rDoc.Delete(m_pPosition); rDoc.SetUndoContext(m_Context); } }; //---------------------------------------------------------------------------- // base class for undoing replacement actions // instantiate derived class and add to UndoData stack before performing action template class UndoReplaceNode : public UndoItem { public: UndoReplaceNode(D& rDoc, T* pPosition, bool bTerminator) : UndoItem(rDoc, pPosition, bTerminator) { // --- save datum for undo/redo m_Datum = *m_pPosition; } void Undo(D& rDoc) { T temp = *m_pPosition; *m_pPosition = m_Datum; m_Datum = temp; rDoc.SetUndoContext(m_Context); } void Redo(D& rDoc) { Undo(rDoc); } }; //---------------------------------------------------------------------------- // base class for storing undo actions // application derives from this class template class UndoData { D& m_rDoc; bool m_bDiscardedUndos; bool m_bUndoEnabled; std::deque*> m_UndoDeque; std::stack*> m_RedoStack; void DeleteAllRedoActions(); int m_nMaxUndos; public: UndoData(D& rDoc, int nMaxUndos); ~UndoData(); void AddUndoNode(UndoNode*pUndoNode);// adds action that can be undone void UndoLastAction(); // undoes the most recent action void RedoLastUndo(); // redoes the most recent undo void EnableUndo() { m_bUndoEnabled = true; } void DisableUndo() { m_bUndoEnabled = false; } bool IsUndoEnabled() const { return m_bUndoEnabled; } bool IsUndoDataStored() const { return !m_UndoDeque.empty(); } bool WasUndoDataDiscarded() const { return m_bDiscardedUndos; } bool IsRedoDataStored() const { return !m_RedoStack.empty(); } void DeleteAllUndoActions(); // call when saving, loading new, etc. }; template UndoData::UndoData(D& rDoc, int nMaxUndos) : m_rDoc(rDoc), m_bDiscardedUndos(false), m_bUndoEnabled(true), m_nMaxUndos(nMaxUndos) { } template UndoData::~UndoData() { DeleteAllUndoActions(); DeleteAllRedoActions(); } template void UndoData::DeleteAllUndoActions() { while (!m_UndoDeque.empty()) { delete m_UndoDeque.back(); m_UndoDeque.pop_back(); } } template void UndoData::DeleteAllRedoActions() { while (!m_RedoStack.empty()) { delete m_RedoStack.top(); m_RedoStack.pop(); } } template void UndoData::AddUndoNode(UndoNode* pUndoNode) { if (pUndoNode != 0) { // --- clean up the undos saved for possible redos DeleteAllRedoActions(); // --- prevent the undo deque from growing too large if (m_UndoDeque.size() >= m_nMaxUndos) { while (!m_UndoDeque.empty() && (m_UndoDeque.size() >= m_nMaxUndos || m_UndoDeque.front()->Terminator() == false)) { delete m_UndoDeque.front(); m_UndoDeque.pop_front(); } m_bDiscardedUndos = true; } m_UndoDeque.push_back(pUndoNode); } } template void UndoData::UndoLastAction() { bool bTerminal = false; while (!bTerminal && !m_UndoDeque.empty()) { m_UndoDeque.back()->Undo(m_rDoc); m_RedoStack.push(m_UndoDeque.back()); bTerminal = m_UndoDeque.back()->Terminator(); m_UndoDeque.pop_back(); } } template void UndoData::RedoLastUndo() { bool bTerminal = false; while (!bTerminal && !m_RedoStack.empty()) { m_RedoStack.top()->Redo(m_rDoc); m_UndoDeque.push_back(m_RedoStack.top()); m_RedoStack.pop(); if (!m_RedoStack.empty()) bTerminal = m_RedoStack.top()->Terminator(); } } // ---- operator placement new supports undo enable/disable template void* UndoNode::operator new(size_t sz, UndoData* pData) { void* p = 0; if (pData->IsUndoEnabled()) p = ::operator new (sz); return p; } } // namespace DDJCProgrammingColumnUndo #endif Listing Two // ------- songundo.h #ifndef SONGUNDO_H #define SONGUNDO_H #include "undo.h" #include "FakeBookDoc.h" namespace undo = DDJCProgrammingColumnUndo; // -------------------------------------------------------------------------- class UndoInsertEvent : public undo::UndoInsertNode { public: UndoInsertEvent(Event* pEvent, bool bTerminator) : undo::UndoInsertNode (*pFakeBookDoc, pEvent, bTerminator) { } }; // -------------------------------------------------------------------------- class UndoDeleteEvent : public undo::UndoDeleteNode { public: UndoDeleteEvent(Event* pEvent, bool bTerminator) : undo::UndoDeleteNode (*pFakeBookDoc, pEvent, bTerminator) { } }; // -------------------------------------------------------------------------- class UndoReplaceEvent : public undo::UndoReplaceNode { public: UndoReplaceEvent(Event* pEvent, bool bTerminator) : undo::UndoReplaceNode (*pFakeBookDoc, pEvent, bTerminator) { } }; // -------------------------------------------------------------------------- class UndoSongData : public undo::UndoData { public: explicit UndoSongData(int nMaxUndos) : undo::UndoData(*pFakeBookDoc, nMaxUndos) { } void AddInsertEventUndo(Event* pEvent, int nCount, bool bTerminator = true) { for (int i = 0; i < nCount; i++) { AddUndoNode(new (this) UndoInsertEvent(pEvent++, bTerminator)); bTerminator = false; } } void AddDeleteEventUndo(Event* pEvent, int nCount, bool bTerminator = true) { for (int i = 0; i < nCount; i++) { AddUndoNode(new (this) UndoDeleteEvent(pEvent, bTerminator)); bTerminator = false; } } void AddReplaceEventUndo(Event* pEvent, bool bTerminator = true) { AddUndoNode(new (this) UndoReplaceEvent(pEvent, bTerminator)); } }; #endif 7