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 <deque>
#include <stack>

namespace DDJCProgrammingColumnUndo {
//------------------------------------------------------------------------------
// UndoNode<D>: base class for all undo actions

template <class D> class UndoData;
template <class D>
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<D>* 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 D, class T, class C>
class UndoItem : public UndoNode<D>
{
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<D>(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 D, class T, class C>
class UndoInsertNode : public UndoItem<D,T,C>
{
public:
    UndoInsertNode(D& rDoc, T* pPosition, bool bTerminator) : 
            UndoItem<D,T,C>(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 D, class T, class C>
class UndoDeleteNode : public UndoItem<D,T,C>
{
public:
    UndoDeleteNode(D& rDoc, T* pPosition, bool bTerminator) : 
        UndoItem<D,T,C>(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 D, class T, class C>
class UndoReplaceNode : public UndoItem<D,T,C>
{
public:
    UndoReplaceNode(D& rDoc, T* pPosition, bool bTerminator) : 
        UndoItem<D,T,C>(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 D>
class UndoData
{
    D& m_rDoc;
    bool m_bDiscardedUndos;
    bool m_bUndoEnabled;
    std::deque<UndoNode<D>*> m_UndoDeque;
    std::stack<UndoNode<D>*> m_RedoStack;
    void DeleteAllRedoActions();
    int m_nMaxUndos;
public:
    UndoData(D& rDoc, int nMaxUndos);
    ~UndoData();
    void AddUndoNode(UndoNode<D>*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 <class D>
UndoData<D>::UndoData(D& rDoc, int nMaxUndos) : m_rDoc(rDoc), 
    m_bDiscardedUndos(false), m_bUndoEnabled(true), m_nMaxUndos(nMaxUndos)
{
}
template <class D>
UndoData<D>::~UndoData()
{
    DeleteAllUndoActions();
    DeleteAllRedoActions();
}
template <class D>
void UndoData<D>::DeleteAllUndoActions()
{
    while (!m_UndoDeque.empty())    {
        delete m_UndoDeque.back();
        m_UndoDeque.pop_back();
    }
}
template <class D>
void UndoData<D>::DeleteAllRedoActions()
{
    while (!m_RedoStack.empty())    {
        delete m_RedoStack.top();
        m_RedoStack.pop();
    }
}
template <class D>
void UndoData<D>::AddUndoNode(UndoNode<D>* 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 <class D>
void UndoData<D>::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 <class D>
void UndoData<D>::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 <class D>
void* UndoNode<D>::operator new(size_t sz, UndoData<D>* 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<CFakeBookDoc, Event, CFakeBookDoc::ViewContext>
{
public:
    UndoInsertEvent(Event* pEvent, bool bTerminator) : 
        undo::UndoInsertNode<CFakeBookDoc, Event, CFakeBookDoc::ViewContext>
            (*pFakeBookDoc, pEvent, bTerminator)
        {   }
};
// --------------------------------------------------------------------------
class UndoDeleteEvent : public 
        undo::UndoDeleteNode<CFakeBookDoc, Event, CFakeBookDoc::ViewContext>
{
public:
    UndoDeleteEvent(Event* pEvent, bool bTerminator) : 
        undo::UndoDeleteNode<CFakeBookDoc, Event, CFakeBookDoc::ViewContext>
            (*pFakeBookDoc, pEvent, bTerminator)
        {   }
};
// --------------------------------------------------------------------------
class UndoReplaceEvent : public 
        undo::UndoReplaceNode<CFakeBookDoc, Event, CFakeBookDoc::ViewContext>
{
public:
    UndoReplaceEvent(Event* pEvent, bool bTerminator) : 
        undo::UndoReplaceNode<CFakeBookDoc, Event, CFakeBookDoc::ViewContext>
            (*pFakeBookDoc, pEvent, bTerminator)
        {   }
};
// --------------------------------------------------------------------------
class UndoSongData : public undo::UndoData<CFakeBookDoc>
{
public:
   explicit UndoSongData(int nMaxUndos) : 
                undo::UndoData<CFakeBookDoc>(*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


