Click here to Skip to main content
15,891,253 members
Articles / Programming Languages / C++

A Simple, Action Based, Undo/Redo Framework

Rate me:
Please Sign up or sign in to vote.
4.51/5 (38 votes)
16 Feb 2013CPOL5 min read 97.7K   1.9K   104  
How to use a simple, action based, undo/redo framework
/*
***************************************************************************

kis v 1

(c) 2002-2006, Florin DUMITRESCU

mailto: fdproxy@gmail.com

$Workfile: kis_Action.cpp $ 

***************************************************************************
*/


#include "stdafx.h"
#include <deque>
#include <vector>
#include <assert.h>

#define protected public
#include "kis.h"


namespace
{


using namespace kis;


//-------------------------------------------------------------------------
/** [To be supplied.] */ 
class C_StackOfSPAction
{

public:
  C_StackOfSPAction()
  :
  m_MaxBytesCount( 0xffff ),
  m_UsedBytesCount( 0 )
  {
    // nop
  }

  ~C_StackOfSPAction()
  {
    // nop
  }

  void SetMaxBytesCount( const unsigned int a_MaxBytesCount )
  {
    if ( m_MaxBytesCount == a_MaxBytesCount )
      return;
    m_MaxBytesCount = a_MaxBytesCount;
    while ( ! m_Deque.empty() && m_UsedBytesCount > m_MaxBytesCount )
      _PopFront();
  }

  unsigned int GetMaxBytesCount() const
  {
    return m_MaxBytesCount;
  }

  unsigned int GetUsedBytesCount() const
  {
    return m_UsedBytesCount;
  }

  unsigned int GetCount() const
  {
    return static_cast< unsigned int >( m_Deque.size() );
  }

  bool HasRoom( const SP_Action a_spAction ) const
  {
    if ( m_MaxBytesCount == 0 )
      return true;
    else
      return m_MaxBytesCount >= a_spAction->GetBytesCount();
  }

  /** Returns true if the action was stored. */
  bool Push( SP_Action a_spAction )
  {
    // fake store
    if ( m_MaxBytesCount == 0 )
      return true;
    // how much space needed?
    unsigned int ActionBytesCount = a_spAction->GetBytesCount();
    // make room
    while ( ! m_Deque.empty() && (m_MaxBytesCount - m_UsedBytesCount) < ActionBytesCount )
      _PopFront();
    // got enough space?
    if ( (m_MaxBytesCount - m_UsedBytesCount) < ActionBytesCount )
      return false; // i guess not
    m_Deque.push_back( a_spAction );
    m_UsedBytesCount += ActionBytesCount;
    return true;
  }

  void Pop()
  {
    if ( m_Deque.empty() )
      return;
    assert( m_UsedBytesCount );
    m_UsedBytesCount -= m_Deque.back()->GetBytesCount();
    m_Deque.pop_back();
  }

  SP_Action Top()
  {
    assert( ! IsEmpty() );
    return m_Deque.back();
  }

  bool IsEmpty() const
  {
    return m_Deque.size() <= 0;
  }

  void Empty()
  {
    while ( m_Deque.size() )
      Pop();
  }

  const SP_Action At( unsigned int a_Idx ) const
  {
    unsigned int Idx = static_cast< unsigned int >( m_Deque.size() - a_Idx - 1 );
    return m_Deque[ Idx ];
  }

protected:
  unsigned int m_MaxBytesCount;
  unsigned int m_UsedBytesCount;
  std::deque< SP_Action > m_Deque;

  void _PopFront()
  {
    if ( m_Deque.empty() )
      return;
    assert( m_UsedBytesCount );
    m_UsedBytesCount -= m_Deque.back()->GetBytesCount();
    m_Deque.pop_front();
  }

};


//-------------------------------------------------------------------------
/** [To be supplied.] */
class C_ActionExecutor_Default : public kis::C_ActionExecutor
{

public:
  
  C_ActionExecutor_Default()
  :
  m_RefCount( 0 )
  {
    //
  }

  /*override*/ unsigned long AddRef()
  {
    return ++m_RefCount;
  }

  /*override*/ unsigned long Release()
  {
    if ( ! --m_RefCount )
    {
      delete this;
      return 0;
    }
    return m_RefCount;
  }

  /*override*/ void SetMaxBytesCount( unsigned int a_MaxBytesCount )
  {
    m_UnexecuteStack.SetMaxBytesCount( a_MaxBytesCount );
    m_ReexecuteStack.SetMaxBytesCount( a_MaxBytesCount );
  }

  /*override*/ unsigned int GetMaxBytesCount() const
  {
    return m_UnexecuteStack.GetMaxBytesCount();
  }

  /*override*/ void Execute( const SP_Action a_spAction )
  {
    if ( ! a_spAction )
      return;

    SP_Action spAction = a_spAction->Clone();

    if ( ! spAction->HasInput() )
      if ( ! spAction->AcquireInput() )
        return;

    if ( spAction->IsNull() )
      return;

    if ( spAction->Execute() )
    {
      if ( spAction->IsUnexecutable() )
      {
        m_ReexecuteStack.Empty(); // the action was executed: the reexecute stack is now invalid
        if ( spAction->HasUnexecute() )
        {
          if ( m_UnexecuteStack.HasRoom( spAction ) )
          {
            m_UnexecuteStack.Push( spAction ); // unexecute information has been built: store it
          }
          else
          {
            // todo: call back: not enough space
            m_UnexecuteStack.Empty(); // unexecute information could not be pushed: the unexecute stack is invalid
          }
        }
        else
        {
          m_UnexecuteStack.Empty(); // unexecute information has not been built: the unexecute stack is invalid
        }
      }
    }
  }

  /*override*/ void Unexecute( const unsigned int a_ActionsCount )
  {
    for ( unsigned int i = 0; i < a_ActionsCount && ! m_UnexecuteStack.IsEmpty(); ++i )
    {
      SP_Action spAction = m_UnexecuteStack.Top();
      m_UnexecuteStack.Pop();
      spAction->Unexecute();
      if ( spAction->HasReexecute() )
      {
        if ( ! m_ReexecuteStack.Push( spAction ) )
          m_ReexecuteStack.Empty();
      }
      else
      {
        m_ReexecuteStack.Empty();
      }
    }
  }

  /*override*/ unsigned int GetUnexecuteCount() const
  {
    return m_UnexecuteStack.GetCount();
  }

  /*override*/ void GetUnexecuteName( unsigned char* a_Name, const unsigned char a_MaxNameLen, const unsigned int a_Idx ) const
  {
    m_UnexecuteStack.At( a_Idx )->GetName( a_Name, a_MaxNameLen );
  }

  /*override*/ void Reexecute( const unsigned int a_ActionsCount )
  {
    for ( unsigned int i = 0; i < a_ActionsCount && ! m_ReexecuteStack.IsEmpty(); ++i )
    {
      SP_Action spAction = m_ReexecuteStack.Top();
      m_ReexecuteStack.Pop();
      spAction->Reexecute();
      if ( spAction->HasUnexecute() )
      {
        if ( ! m_UnexecuteStack.Push( spAction ) )
          m_UnexecuteStack.Empty();
      }
      else
      {
        m_UnexecuteStack.Empty();
      }
    }
  }

  /*override*/ unsigned int GetReexecuteCount() const
  {
    return m_ReexecuteStack.GetCount();
  }

  /*override*/ void GetReexecuteName( unsigned char* a_Name, const unsigned char a_MaxNameLen, const unsigned int a_Idx ) const
  {
    m_ReexecuteStack.At( a_Idx )->GetName( a_Name, a_MaxNameLen );
  }

protected:
  
  unsigned long m_RefCount;
  C_StackOfSPAction m_UnexecuteStack;
  C_StackOfSPAction m_ReexecuteStack;
  
  /*override*/ ~C_ActionExecutor_Default()
  {
    //
  }

};


} // namespace


KIS_BEG


//-------------------------------------------------------------------------
/** [To be supplied.] */
KIS_API SP_ActionExecutor CreateActionExecutor_Default()
{
  return SP_ActionExecutor( new C_ActionExecutor_Default );
}


KIS_END

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
zdf
Romania Romania
Just a humble programmer.

Comments and Discussions