コマンドチェインの登録を途中で中止できるように
コマンドのグループ化に対応できるようになりましたが、途中でエラーが発生したときなどに、キャンセルするということができませんでした
コマンドチェインを途中でキャンセルできるようにしてみましょう
Undo コマンド管理クラスの実装
#define INVALID_COMMAND_ID (( uint32_t )-1) #define INVALID_CHAIN_ID (( uint32_t )-1) #define MAXIMUM_CHAIN_LEVEL (( uint32_t )-1) class cundoredoitem { uint32_t m_chain_id; uint32_t m_chain_level; uint32_t m_id; IRestoreObj* m_pRestoreObj; bool dispatch( uint32_t uMsg ); public: void detach(); bool attach( uint32_t chain_id, uint32_t chain_level, uint32_t id, IRestoreObj* pRestoreObj ); bool get_object( IRestoreObj** ppRestoreObj ); bool undo(); bool redo(); uint32_t chain_id() const; uint32_t chain_level() const; bool operator==( uint32_t id ) const; cundoredoitem& operator=( const cundoredoitem& rhs ); cundoredoitem(); cundoredoitem( uint32_t chain_id, uint32_t chain_level, uint32_t id, IRestoreObj* pRestoreObj ); cundoredoitem( const cundoredoitem& rhs ); ~cundoredoitem(); };
inline uint32_t cundoredoitem::chain_id() const { return m_chain_id; } inline uint32_t cundoredoitem::chain_level() const { return m_chain_level; } inline bool cundoredoitem::operator==( uint32_t id ) const { return ( m_id == id ); } inline cundoredoitem& cundoredoitem::operator=( const cundoredoitem& rhs ){ if( this != &rhs ){ m_chain_id = rhs.m_chain_id; m_chain_level = rhs.m_chain_level; m_id = rhs.m_id; m_pRestoreObj = rhs.m_pRestoreObj; } return *this; } inline cundoredoitem::cundoredoitem(): m_chain_id(INVALID_CHAIN_ID), m_chain_level(0), m_id(INVALID_COMMAND_ID), m_pRestoreObj(NULL) { } inline cundoredoitem::cundoredoitem( uint32_t chain_id, uint32_t chain_level, uint32_t id, IRestoreObj* pRestoreObj ): m_chain_id(chain_id), m_chain_level(chain_level), m_id(id), m_pRestoreObj(pRestoreObj) { } inline cundoredoitem::cundoredoitem( const cundoredoitem& rhs ): m_chain_id(rhs.m_chain_id), m_chain_level(rhs.m_chain_level), 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_chain_id = INVALID_CHAIN_ID; m_chain_level = 0; m_id = INVALID_COMMAND_ID; } inline bool cundoredoitem::attach( uint32_t chain_id, uint32_t chain_level, uint32_t id, IRestoreObj* pRestoreObj ){ bool result = true; if(( m_chain_id != chain_id ) ||( m_chain_level != chain_level ) ||( m_id != id ) ||( m_pRestoreObj != pRestoreObj ) ){ detach(); if( pRestoreObj ){ m_chain_id = chain_id; m_chain_level = chain_level; m_id = id; pRestoreObj->add_ref(); m_pRestoreObj = pRestoreObj; result = dispatch( UNDOREDO_ATTACHED ); if( !result ){ m_pRestoreObj->release(); m_pRestoreObj = NULL; m_chain_id = INVALID_CHAIN_ID; m_chain_level = 0; 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 リスト管理クラスの実装
class cundoredo { std::deque<cundoredoitem> m_redo; std::deque<cundoredoitem> m_undo; uint32_t m_chain_id; uint32_t m_chain_level; 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 chain_begin(); bool chain_end(); bool chain_cancel(); 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_chain_id(INVALID_CHAIN_ID), m_chain_level(0), 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; } else if( m_chain_id == INVALID_CHAIN_ID ){ result = false; } if( result ){ cundoredoitem item; result = item.attach( m_chain_id, m_chain_level, ++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_chain_id = INVALID_CHAIN_ID; m_chain_level = 0; 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; do{ undo.push_back( m_undo.back()); m_undo.pop_back(); }while( !m_undo.empty() &&( m_undo.rbegin()->chain_id() == undo.rbegin()->chain_id())); 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; do{ redo.push_back( m_redo.back()); m_redo.pop_back(); }while( !m_redo.empty() &&( m_redo.rbegin()->chain_id() == redo.rbegin()->chain_id())); 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; } bool cundoredo::chain_begin(){ bool result = true; if( !m_chain_level ){ if( m_chain_id + 1 == INVALID_CHAIN_ID ){ result = false; } if( result ){ ++m_chain_level; ++m_chain_id; } } else{ if( m_chain_level + 1 >= MAXIMUM_CHAIN_LEVEL ){ result = false; } if( result ){ ++m_chain_level; } } return result; } bool cundoredo::chain_end(){ bool result = true; if( !m_chain_level ){ result = false; } if( result ){ --m_chain_level; } return result; } bool cundoredo::chain_cancel(){ bool result = true; if( !m_chain_level ){ result = false; } if( result ){ while( !m_undo.empty()){ std::deque<cundoredoitem>::reverse_iterator p = m_undo.rbegin(); if(( p->chain_id() != m_chain_id )||( p->chain_level() != m_chain_level )){ break; } p->undo(); p->detach(); m_undo.pop_back(); } --m_chain_level; } return result; }
テストコード
int main(){ std::string s; cundoredo undoredo; undoredo.chain_begin(); string_push_back( undoredo, s, 'A' ); string_push_back( undoredo, s, 'B' ); string_push_back( undoredo, s, 'C' ); undoredo.chain_begin(); string_push_back( undoredo, s, '1' ); string_push_back( undoredo, s, '2' ); string_push_back( undoredo, s, '3' ); undoredo.chain_cancel(); undoredo.chain_end(); undoredo.chain_begin(); string_push_back( undoredo, s, 'D' ); undoredo.chain_end(); return 0; }
出力
redo: "" → "A" redo: "A" → "AB" redo: "AB" → "ABC" redo: "ABC" → "ABC1" redo: "ABC1" → "ABC12" redo: "ABC12" → "ABC123" undo: "ABC123" → "ABC12" undo: "ABC12" → "ABC1" undo: "ABC1" → "ABC" redo: "ABC" → "ABCD"