Click here to Skip to main content
15,881,812 members
Articles / Desktop Programming / WTL

Undo Manager

Rate me:
Please Sign up or sign in to vote.
4.92/5 (10 votes)
12 Sep 20019 min read 118.9K   3.4K   72  
An article about managing undo and redo actions
#ifndef GDIGRAPH_H
#define GDIGRAPH_H

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

#ifndef _UNDOMGR_H_
	#error gdigraph.h requires undomgr.h to be included first
#endif

#include <map>

class CGUIObject
{
public:
	POINT		m_pt;
	SIZE		m_size;
	COLORREF	m_crColor;

	CGUIObject() : m_crColor(RGB(0,0,0)) { m_pt.x = m_pt.y = 100; m_size.cx = m_size.cy = 20; }
	CGUIObject(POINT pt) : m_pt(pt), m_crColor(RGB(0,0,0)) { m_size.cx = m_size.cy = 20; }
	CGUIObject(POINT pt, SIZE size) : m_pt(pt), m_size(size), m_crColor(RGB(0,0,0)) {}
	CGUIObject(POINT pt, SIZE size, COLORREF crColor) : m_pt(pt), m_size(size), m_crColor(crColor) {}

	virtual ~CGUIObject() {}

	RECT GetRect()
	{
		RECT rc = { m_pt.x - m_size.cx / 2, m_pt.y - m_size.cy / 2, m_pt.x + m_size.cx / 2, m_pt.y + m_size.cy / 2 };
		return rc;
	}
	void SetColor(COLORREF	crColor)
	{
		m_crColor = crColor;
	}
	COLORREF GetColor()
	{
		return m_crColor;
	}
	void SetSize(SIZE size)
	{
		m_size = size;
	}
	SIZE GetSize()
	{
		return m_size;
	}
	void MoveTo(POINT pt)
	{
		m_pt = pt;
	}

	virtual void Delete() = 0;
	virtual void Draw(HDC hDc) = 0;
	virtual LPCWSTR Description() = 0;
};


typedef std::map<long, CGUIObject*> GUIObjectMap;

struct DrawFunctor
{
	HDC m_dc;

	DrawFunctor(HDC dc) : m_dc(dc) {}

	void operator()(std::pair<long, CGUIObject*> p)
	{
		p.second->Draw(m_dc);
	}
};




class CGUIBox : public CGUIObject
{
public:
	CGUIBox() : CGUIObject() {}
	CGUIBox(POINT pt) : CGUIObject(pt) {}
	CGUIBox(POINT pt, SIZE size) : CGUIObject(pt, size) {}
	CGUIBox(POINT pt, SIZE size, COLORREF crColor) : CGUIObject(pt, size, crColor) {}

	virtual ~CGUIBox() {}

	void Delete() { delete this; }

	virtual void Draw(HDC hDc)
	{
		CDCHandle dc(hDc);
		CBrush brush;
		brush.CreateSolidBrush(m_crColor);
		RECT rc = GetRect();
		dc.FillRect(&rc, brush);
	}

	virtual LPCWSTR Description()
	{
		return L"Create box";
	}
};

class CGUIEllipse : public CGUIObject
{
public:
	CGUIEllipse() : CGUIObject() {}
	CGUIEllipse(POINT pt) : CGUIObject(pt) {}
	CGUIEllipse(POINT pt, SIZE size) : CGUIObject(pt, size) {}
	CGUIEllipse(POINT pt, SIZE size, COLORREF crColor) : CGUIObject(pt, size, crColor) {}

	virtual ~CGUIEllipse() {}

	void Delete() { delete this; }

	virtual void Draw(HDC hDc)
	{
		CDCHandle dc(hDc);
		RECT rc = GetRect();
		dc.Ellipse(&rc);
	}

	virtual LPCWSTR Description()
	{
		return L"Create ellipse";
	}
};

class CGUIRoundRect : public CGUIObject
{
public:
	CGUIRoundRect() : CGUIObject() {}
	CGUIRoundRect(POINT pt) : CGUIObject(pt) {}
	CGUIRoundRect(POINT pt, SIZE size) : CGUIObject(pt, size) {}
	CGUIRoundRect(POINT pt, SIZE size, COLORREF crColor) : CGUIObject(pt, size, crColor) {}

	virtual ~CGUIRoundRect() {}

	void Delete() { delete this; }

	virtual void Draw(HDC hDc)
	{
		CDCHandle dc(hDc);
		POINT pt = { m_size.cx / 4, m_size.cy / 4 };
		RECT rc = GetRect();
		dc.RoundRect(&rc, pt);
	}

	virtual LPCWSTR Description()
	{
		return  L"Create round rect";
	}
};

class GUIContext
{
public:
	void static Initialize(GUIObjectMap* pA, GUIObjectMap* pD)
	{
		pActiveMap = pA;
		pDeletedMap = pD;
	}

	static GUIObjectMap* pActiveMap;
	static GUIObjectMap* pDeletedMap;
};

GUIObjectMap* GUIContext::pActiveMap;
GUIObjectMap* GUIContext::pDeletedMap;

//
// Helper funcations
//

HRESULT CreateUndoUnit(long id, IOleUndoUnit** ppUU);
HRESULT CreateRedoUnit(long id, IOleUndoUnit** ppUU);
HRESULT CreateGroupUnit(IOleParentUndoUnit** ppPUU);


//
// Simple undo / redo units
//

class ATL_NO_VTABLE CUndoUnit : 
	public CComObjectRootEx<CComSingleThreadModel>,
	public IOleUndoUnitImpl<CUndoUnit>,
	public CComClassID<>
{
public:
	CUndoUnit() { }

BEGIN_COM_MAP(CUndoUnit)
	COM_INTERFACE_ENTRY(IOleUndoUnit)
END_COM_MAP()

	long m_id;

	HRESULT IOleUndoUnitImpl_Do(IOleUndoManager* /*pUndoManager*/)
	{
		GUIObjectMap::iterator iter = GUIContext::pActiveMap->find(m_id);
		if ( iter != GUIContext::pActiveMap->end() )
		{
			GUIContext::pDeletedMap->insert(*iter);
			GUIContext::pActiveMap->erase(iter);
			return S_OK;
		}
		// The object map has become corrupt.
		return E_ABORT;
	}

	HRESULT IOleUndoUnitImpl_CreateUndoUnit(IOleUndoUnit** ppUU)
	{
		HRESULT hr = CreateRedoUnit(m_id, ppUU);
		return SUCCEEDED(hr) ? S_OK : E_ABORT;
	}

	STDMETHOD(GetDescription)(BSTR* pBstr)
	{
		GUIObjectMap::iterator iter = GUIContext::pActiveMap->find(m_id);
		if ( iter != GUIContext::pActiveMap->end() )
		{
			*pBstr = ::SysAllocString((*iter).second->Description());
			return S_OK;
		}
		return E_FAIL;
	}
};

class ATL_NO_VTABLE CRedoUnit : 
	public CComObjectRootEx<CComSingleThreadModel>,
	public IOleUndoUnitImpl<CRedoUnit>,
	public CComClassID<>
{
public:
	CRedoUnit() { }

BEGIN_COM_MAP(CRedoUnit)
	COM_INTERFACE_ENTRY(IOleUndoUnit)
END_COM_MAP()

	long m_id;

	HRESULT IOleUndoUnitImpl_Do(IOleUndoManager* /*pUndoManager*/)
	{
		GUIObjectMap::iterator iter = GUIContext::pDeletedMap->find(m_id);
		if ( iter != GUIContext::pDeletedMap->end() )
		{
			GUIContext::pActiveMap->insert(*iter);
			GUIContext::pDeletedMap->erase(iter);
			return S_OK;
		}
		// The object map has become corrupt.
		return E_ABORT;
	}

	HRESULT IOleUndoUnitImpl_CreateUndoUnit(IOleUndoUnit** ppUU)
	{
		HRESULT hr = CreateUndoUnit(m_id, ppUU);
		return SUCCEEDED(hr) ? S_OK : E_ABORT;
	}

	STDMETHOD(GetDescription)(BSTR* pBstr)
	{
		GUIObjectMap::iterator iter = GUIContext::pDeletedMap->find(m_id);
		if ( iter != GUIContext::pDeletedMap->end() )
		{
			*pBstr = ::SysAllocString((*iter).second->Description());
			return S_OK;
		}
		return E_FAIL;
	}
};

//
// A simple parent unit acting as a command on a group
// of GUIObjects, this can be used for both Undo and Redo
//

class ATL_NO_VTABLE CGroupUnit : 
	public CComObjectRootEx<CComSingleThreadModel>,
	public IOleParentUndoUnitImpl<CGroupUnit>,
	public CComClassID<>
{
public:
	CGroupUnit() { }

BEGIN_COM_MAP(CGroupUnit)
	COM_INTERFACE_ENTRY(IOleParentUndoUnit)
	COM_INTERFACE_ENTRY(IOleUndoUnit)
END_COM_MAP()

	HRESULT IOleParentUndoUnitImpl_CreateParentUndoUnit(IOleParentUndoUnit** ppPUU)
	{
		HRESULT hr = CreateGroupUnit(ppPUU);
		return SUCCEEDED(hr) ? S_OK : E_ABORT;
	}

	STDMETHOD(GetDescription)(BSTR* pBstr)
	{
		*pBstr = ::SysAllocString(L"Create group");
		return S_OK;
	}
};




HRESULT CreateUndoUnit(long id, IOleUndoUnit** ppUU)
{
	CComObject<CUndoUnit>* pObj = NULL;
	HRESULT hr = pObj->CreateInstance(&pObj);

	if (SUCCEEDED(hr))
	{
		pObj->AddRef();
		pObj->m_id = id;

		hr = pObj->QueryInterface(ppUU);

		pObj->Release();
	}
	return hr;
}

HRESULT CreateRedoUnit(long id, IOleUndoUnit** ppUU)
{
	CComObject<CRedoUnit>* pObj = NULL;
	HRESULT hr = pObj->CreateInstance(&pObj);

	if (SUCCEEDED(hr))
	{
		pObj->AddRef();
		pObj->m_id = id;

		hr = pObj->QueryInterface(ppUU);

		pObj->Release();
	}
	return hr;
}

HRESULT CreateGroupUnit(IOleParentUndoUnit** ppPUU)
{
	CComObject<CGroupUnit>* pObj = NULL;
	HRESULT hr = pObj->CreateInstance(&pObj);

	if (SUCCEEDED(hr))
	{
		pObj->AddRef();

		hr = pObj->QueryInterface(ppPUU);

		pObj->Release();
	}
	return hr;
}


#endif GDIGRAPH_H

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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Sweden Sweden
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions