コマンドチェインに名前を付ける

Undo/Redoの履歴を表示したいときなどがあります
このようなときコマンドのグループに名前が付いていると便利です
コマンドチェインに名前を付けられるようにしてみましょう

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_begin();
	bool	undo_end();
	bool	undo();
	bool	redo_begin();
	bool	redo_end();
	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_begin(){
	return	dispatch( UNDOREDO_UNDO_BEGIN );
}
inline bool	cundoredoitem::undo_end(){
	return	dispatch( UNDOREDO_UNDO_END );
}
inline bool	cundoredoitem::undo(){
	return	dispatch( UNDOREDO_UNDO );
}
inline bool	cundoredoitem::redo_begin(){
	return	dispatch( UNDOREDO_REDO_BEGIN );
}
inline bool	cundoredoitem::redo_end(){
	return	dispatch( UNDOREDO_REDO_END );
}
inline bool	cundoredoitem::redo(){
	return	dispatch( UNDOREDO_REDO );
}

Undo リスト管理の実装

class cundoredo {
	std::deque<cundoredoitem>	m_redo;
	std::deque<cundoredoitem>	m_undo;
	std::map<uint32_t,std::string>	m_chains;
	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( const std::string&	chain_name );
	bool	chain_end();
	bool	chain_cancel();
	bool	undo();
	bool	redo();
	bool	is_undo_available() const;
	bool	is_redo_available() const;
	size_t	get_undo_chain_count() const;
	size_t	get_redo_chain_count() const;
	std::vector<std::string>	get_undo_chains() const;
	std::vector<std::string>	get_redo_chains() const;
	
	cundoredo();
	~cundoredo();
private:
	cundoredo( const cundoredo& );
	cundoredo&	operator=( const cundoredo& );
};
cundoredo::cundoredo():
m_redo(),
m_undo(),
m_chains(),
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 ){
				m_chains.erase( p->chain_id());
				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_chains.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() const {
	return	!m_undo.empty();
}
bool	cundoredo::is_redo_available() const {
	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 ){
			if( p == undo.begin()){
				p->undo_begin();
			}
			p->undo();
			std::deque<cundoredoitem>::iterator	q = p;
			if( ++q == undo.end()){
				p->undo_end();
			}
		}
	}
	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 ){
			if( p == redo.rbegin()){
				p->redo_begin();
			}
			p->redo();
			std::deque<cundoredoitem>::reverse_iterator	q = p;
			if( ++q == redo.rend()){
				p->redo_end();
			}
		}
	}
	return	true;
}
bool	cundoredo::chain_begin( const std::string&	chain_name ){
	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;
			m_chains[m_chain_id] = chain_name;
		}
	}
	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_chains.erase( m_chain_id );
		--m_chain_level;
	}
	return	result;
}
size_t	cundoredo::get_undo_chain_count() const {
	size_t	chains = 0;
	uint32_t	chain_id_prev = INVALID_CHAIN_ID;
	for( std::deque<cundoredoitem>::const_iterator	p = m_undo.begin(); p != m_undo.end(); ++p ){
		if( !chains ||( chain_id_prev != p->chain_id())){
			chain_id_prev =  p->chain_id();
			++chains;
		}
	}
	return	chains;
}
std::vector<std::string>	cundoredo::get_undo_chains() const {
	std::vector<std::pair<uint32_t,std::string> >	chains;
	chains.reserve( m_undo.size());
	for( std::deque<cundoredoitem>::const_reverse_iterator	p = m_undo.rbegin(); p != m_undo.rend(); ++p ){
		if( chains.empty() ||( chains.rbegin()->first != p->chain_id())){
			chains.push_back( std::make_pair( p->chain_id(), m_chains.find( p->chain_id())->second ));
		}
	}
	std::vector<std::string>	result;
	result.reserve( chains.size());
	for( std::vector<std::pair<uint32_t,std::string> >::iterator	p = chains.begin(); p != chains.end(); ++p ){
		result.push_back( p->second );
	}
	return	result;
}
size_t	cundoredo::get_redo_chain_count() const {
	size_t	chains = 0;
	uint32_t	chain_id_prev = INVALID_CHAIN_ID;
	for( std::deque<cundoredoitem>::const_iterator	p = m_redo.begin(); p != m_redo.end(); ++p ){
		if( !chains ||( chain_id_prev != p->chain_id())){
			chain_id_prev =  p->chain_id();
			++chains;
		}
	}
	return	chains;
}
std::vector<std::string>	cundoredo::get_redo_chains() const {
	std::vector<std::pair<uint32_t,std::string> >	chains;
	chains.reserve( m_redo.size());
	for( std::deque<cundoredoitem>::const_reverse_iterator	p = m_redo.rbegin(); p != m_redo.rend(); ++p ){
		if( chains.empty() ||( chains.rbegin()->first != p->chain_id())){
			chains.push_back( std::make_pair( p->chain_id(), m_chains.find( p->chain_id())->second ));
		}
	}
	std::vector<std::string>	result;
	result.reserve( chains.size());
	for( std::vector<std::pair<uint32_t,std::string> >::iterator	p = chains.begin(); p != chains.end(); ++p ){
		result.push_back( p->second );
	}
	return	result;
}

テストコード

inline void	output_chains( const cundoredo&	undoredo ){
	std::vector<std::string>	undo_chains = undoredo.get_undo_chains();
	std::vector<std::string>	redo_chains = undoredo.get_redo_chains();
	debug_printf( "{\n" );
	for( std::vector<std::string>::reverse_iterator	p = undo_chains.rbegin(); p != undo_chains.rend(); ++p ){
		debug_printf( "    %s\n", p->c_str());
	}
	debug_printf( "---\n" );
	for( std::vector<std::string>::iterator	p = redo_chains.begin(); p != redo_chains.end(); ++p ){
		debug_printf( "    %s\n", p->c_str());
	}
	debug_printf( "}\n" );
}
int	main(){
	std::string	s;
	cundoredo	undoredo;
	undoredo.chain_begin( "[0] ABC" );
	string_push_back( undoredo, s, 'A' );
	string_push_back( undoredo, s, 'B' );
	string_push_back( undoredo, s, 'C' );
	undoredo.chain_end();
	undoredo.chain_begin( "[1] 123" );
	string_push_back( undoredo, s, '1' );
	string_push_back( undoredo, s, '2' );
	string_push_back( undoredo, s, '3' );
	undoredo.chain_end();
	undoredo.chain_begin( "[2] D" );
	string_push_back( undoredo, s, 'D' );
	undoredo.chain_end();
	output_chains( undoredo );
	undoredo.undo();
	output_chains( undoredo );
	undoredo.undo();
	output_chains( undoredo );
	undoredo.undo();
	output_chains( undoredo );
	undoredo.chain_begin( "[3] E" );
	string_push_back( undoredo, s, 'E' );
	undoredo.chain_end();
	output_chains( undoredo );
	return	0;
}

出力

redo: "" → "A"
redo: "A" → "AB"
redo: "AB" → "ABC"
redo: "ABC" → "ABC1"
redo: "ABC1" → "ABC12"
redo: "ABC12" → "ABC123"
redo: "ABC123" → "ABC123D"
{
    [0] ABC
    [1] 123
    [2] D
---
}
undo:{
undo: "ABC123D" → "ABC123"
undo:}
{
    [0] ABC
    [1] 123
---
    [2] D
}
undo:{
undo: "ABC123" → "ABC12"
undo: "ABC12" → "ABC1"
undo: "ABC1" → "ABC"
undo:}
{
    [0] ABC
---
    [1] 123
    [2] D
}
undo:{
undo: "ABC" → "AB"
undo: "AB" → "A"
undo: "A" → ""
undo:}
{
---
    [0] ABC
    [1] 123
    [2] D
}
redo: "" → "E"
{
    [3] E
---
}