初歩的な実装
Undo コマンド
まずは Undo マネージャに登録するコマンドのインターフェイスを決めましょう
Undo マネージャに登録するコマンドはつまり Undo 可能なコマンドということですが、この Undo 可能なコマンドはアプリケーション側が実装するものです
そしてこのコマンドは少なくとも Undo マネージャから Undo と Redo の通知を受け取れるようにしておく必要があります
このコマンドはどのような形式でもいいのですが、ここでは次のようなインターフェイスを使うことにしましょう
#define UNDOREDO_DETACHED 0 #define UNDOREDO_ATTACHED 1 #define UNDOREDO_UNDO 2 #define UNDOREDO_REDO 3 struct IRestoreObj { virtual uint32_t add_ref() = 0; virtual uint32_t release() = 0; virtual void invoke( uint32_t id, uint32_t uMsg ) = 0; };
簡単なラッパも書いておくと、このインターフェイスを毎回一から実装しなくてもよくて楽です
class CRestoreObj : public IRestoreObj { protected: virtual void undo(){} virtual void redo(){} public: virtual uint32_t add_ref(){ return ( uint32_t )++m_cRef; } virtual uint32_t release(){ int32_t cRef = --m_cRef; if( cRef == 0 ){ delete this; return 0; } return ( uint32_t )cRef; } virtual void invoke( uint32_t, uint32_t uMsg ){ if( uMsg == UNDOREDO_DETACHED ){ release(); } else if( uMsg == UNDOREDO_ATTACHED ){ add_ref(); } else if( uMsg == UNDOREDO_UNDO ){ undo(); } else if( uMsg == UNDOREDO_REDO ){ redo(); } } CRestoreObj(): m_cRef(1) { } virtual ~CRestoreObj(){ } private: int32_t m_cRef; CRestoreObj( const CRestoreObj& ); CRestoreObj& operator=( const CRestoreObj& ); };
ここでは通知を一つのメソッドにまとめたインターフェイスを使いますが、別にそれぞれ別のメソッドとするインターフェイスでも構いません
Undo コマンド管理クラスの実装
#define INVALID_COMMAND_ID (( uint32_t )-1) class cundoredoitem { uint32_t m_id; IRestoreObj* m_pRestoreObj; bool dispatch( uint32_t uMsg ); public: void detach(); bool attach( uint32_t id, IRestoreObj* pRestoreObj ); bool get_object( IRestoreObj** ppRestoreObj ); bool undo(); bool redo(); bool operator==( uint32_t id ) const; cundoredoitem& operator=( const cundoredoitem& rhs ); cundoredoitem(); cundoredoitem( uint32_t id, IRestoreObj* pRestoreObj ); cundoredoitem( const cundoredoitem& rhs ); ~cundoredoitem(); };
inline bool cundoredoitem::operator==( uint32_t id ) const { return ( m_id == id ); } inline cundoredoitem& cundoredoitem::operator=( const cundoredoitem& rhs ){ if( this != &rhs ){ m_id = rhs.m_id; m_pRestoreObj = rhs.m_pRestoreObj; } return *this; } inline cundoredoitem::cundoredoitem(): m_id(INVALID_COMMAND_ID), m_pRestoreObj(NULL) { } inline cundoredoitem::cundoredoitem( uint32_t id, IRestoreObj* pRestoreObj ): m_id(id), m_pRestoreObj(pRestoreObj) { } inline cundoredoitem::cundoredoitem( const cundoredoitem& rhs ): m_id(rhs.m_id), m_pRestoreObj(rhs.m_pRestoreObj) { if( m_pRestoreObj ){ m_pRestoreObj->add_ref(); } } inline cundoredoitem::~cundoredoitem(){ if( m_pRestoreObj ){ m_pRestoreObj->release(); m_pRestoreObj = NULL; } } inline bool cundoredoitem::dispatch( uint32_t uMsg ){ bool result = true; if( !m_pRestoreObj ){ result = false; } if( result ){ m_pRestoreObj->invoke( m_id, uMsg ); } return result; } inline void cundoredoitem::detach(){ if( m_pRestoreObj ){ dispatch( UNDOREDO_DETACHED ); m_pRestoreObj->release(); m_pRestoreObj = NULL; } m_id = INVALID_COMMAND_ID; } inline bool cundoredoitem::attach( uint32_t id, IRestoreObj* pRestoreObj ){ bool result = true; if(( m_id != id ) ||( m_pRestoreObj != pRestoreObj ) ){ detach(); if( pRestoreObj ){ m_id = id; pRestoreObj->add_ref(); m_pRestoreObj = pRestoreObj; result = dispatch( UNDOREDO_ATTACHED ); if( !result ){ m_pRestoreObj->release(); m_pRestoreObj = NULL; m_id = INVALID_COMMAND_ID; } } } return result; } inline bool cundoredoitem::get_object( IRestoreObj** ppRestoreObj ){ if( ppRestoreObj ){ *ppRestoreObj = NULL; } bool result = true; if( !ppRestoreObj ){ result = false; } else if( !m_pRestoreObj ){ result = false; } if( result ){ m_pRestoreObj->add_ref(); *ppRestoreObj = m_pRestoreObj; } return result; } inline bool cundoredoitem::undo(){ return dispatch( UNDOREDO_UNDO ); } inline bool cundoredoitem::redo(){ return dispatch( UNDOREDO_REDO ); }
Undo リスト管理クラスの実装
ここではコマンドのリストを Undo用と Redo用の2つに分けて実装しています
class cundoredo { std::deque<cundoredoitem> m_redo; std::deque<cundoredoitem> m_undo; uint32_t m_id; public: bool add_object( IRestoreObj* pRestoreObj, uint32_t* pid ); bool delete_object( uint32_t id ); bool get_object( uint32_t id, IRestoreObj** ppRestoreObj ); bool undo(); bool redo(); bool is_undo_available(); bool is_redo_available(); cundoredo(); ~cundoredo(); private: cundoredo( const cundoredo& ); cundoredo& operator=( const cundoredo& ); };
cundoredo::cundoredo(): m_redo(), m_undo(), m_id(INVALID_COMMAND_ID) { } cundoredo::~cundoredo(){ cundoredo::delete_object( INVALID_COMMAND_ID ); } bool cundoredo::add_object( IRestoreObj* pRestoreObj, uint32_t* pid ){ if( pid ){ pid = 0; } bool result = true; if( !pRestoreObj ){ result = false; } if( result ){ cundoredoitem item; result = item.attach( ++m_id, pRestoreObj ); if( result ){ m_undo.push_back( item ); for( std::deque<cundoredoitem>::iterator p = m_redo.begin(); p != m_redo.end(); ++p ){ p->detach(); } m_redo.clear(); result = m_undo.rbegin()->redo(); } } return result; } bool cundoredo::get_object( uint32_t id, IRestoreObj** ppRestoreObj ){ if( ppRestoreObj ){ *ppRestoreObj = NULL; } bool result = true; if( !ppRestoreObj ){ result = false; } if( result ){ std::deque<cundoredoitem>::iterator p = std::find( m_undo.begin(), m_undo.end(), id ); if( p == m_undo.end()){ result = false; } else{ p = std::find( m_redo.begin(), m_redo.end(), id ); if( p == m_redo.end()){ result = false; } } if( result ){ result = p->get_object( ppRestoreObj ); } } return result; } bool cundoredo::delete_object( uint32_t id ){ bool result = true; if( id == INVALID_COMMAND_ID ){ for( std::deque<cundoredoitem>::iterator p = m_redo.begin(); p != m_redo.end(); ++p ){ p->detach(); } m_redo.clear(); for( std::deque<cundoredoitem>::iterator p = m_undo.begin(); p != m_undo.end(); ++p ){ p->detach(); } m_undo.clear(); m_id = INVALID_COMMAND_ID; } else{ result = false; std::deque<cundoredoitem>::iterator p = std::find( m_undo.begin(), m_undo.end(), id ); if( p != m_undo.end()){ p->detach(); m_undo.erase( p ); result = true; } else{ p = std::find( m_redo.begin(), m_redo.end(), id ); if( p != m_redo.end()){ p->detach(); m_redo.erase( p ); result = true; } } } return result; } bool cundoredo::is_undo_available(){ return !m_undo.empty(); } bool cundoredo::is_redo_available(){ return !m_redo.empty(); } bool cundoredo::undo(){ if( !m_undo.empty()){ std::deque<cundoredoitem> undo; undo.push_back( m_undo.back()); m_undo.pop_back(); for( std::deque<cundoredoitem>::reverse_iterator p = undo.rbegin(); p != undo.rend(); ++p ){ m_redo.push_back( *p ); } for( std::deque<cundoredoitem>::iterator p = undo.begin(); p != undo.end(); ++p ){ p->undo(); } } return true; } bool cundoredo::redo(){ if( !m_redo.empty()){ std::deque<cundoredoitem> redo; redo.push_back( m_redo.back()); m_redo.pop_back(); for( std::deque<cundoredoitem>::reverse_iterator p = redo.rbegin(); p != redo.rend(); ++p ){ m_undo.push_back( *p ); } for( std::deque<cundoredoitem>::reverse_iterator p = redo.rbegin(); p != redo.rend(); ++p ){ p->redo(); } } return true; }
テストコード
void debug_printf( const char* format,... ){ char temp[256] = {}; va_list ap; va_start( ap, format ); _vsnprintf( temp, elementsof( temp ), format, ap ); va_end( ap ); OutputDebugStringA( temp ); } class CRestoreObj_string_push_back : public CRestoreObj { std::string& m_string; char m_c; uint8_t :8; uint8_t :8; uint8_t :8; void push_back( char c ){ m_string.push_back( c ); } void pop_back(){ if( !m_string.empty()){ m_string.erase( m_string.size()-1 ); } } public: void undo(){ std::string string_prev = m_string; pop_back(); debug_printf( "undo: \"%s\" → \"%s\"\n", string_prev.c_str(), m_string.c_str()); } void redo(){ std::string string_prev = m_string; push_back( m_c ); debug_printf( "redo: \"%s\" → \"%s\"\n", string_prev.c_str(), m_string.c_str()); } CRestoreObj_string_push_back( std::string& s, char c ): CRestoreObj(), m_string(s), m_c(c) { } virtual ~CRestoreObj_string_push_back(){ } private: CRestoreObj_string_push_back(); CRestoreObj_string_push_back( const CRestoreObj_string_push_back& ); CRestoreObj_string_push_back& operator=( const CRestoreObj_string_push_back& ); }; inline void string_push_back( cundoredo& undoredo, std::string& s, char c, uint32_t* pid = NULL ){ IRestoreObj* p = new CRestoreObj_string_push_back( s, c ); undoredo.add_object( p, pid ); p->release(); } int main(){ std::string s; cundoredo undoredo; string_push_back( undoredo, s, 'A' ); string_push_back( undoredo, s, 'B' ); string_push_back( undoredo, s, 'C' ); undoredo.undo(); undoredo.undo(); undoredo.redo(); string_push_back( undoredo, s, 'D' ); return 0; }
出力
redo: "" → "A" redo: "A" → "AB" redo: "AB" → "ABC" undo: "ABC" → "AB" undo: "AB" → "A" redo: "A" → "AB" redo: "AB" → "ABD"