Click here to Skip to main content
15,895,084 members
Articles / Programming Languages / C++

Undo and Redo the "Easy" Way

Rate me:
Please Sign up or sign in to vote.
4.95/5 (42 votes)
20 Jun 2004CPOL22 min read 290.5K   8.6K   114  
This article introduces a simple approach to in-memory transactions that can be used to implement Undo and Redo. The technique uses SEH and Virtual Memory and requires only STL and Win32.
// DrawItView.cpp : implementation of the CDrawItView class
//

#include "stdafx.h"
#pragma warning (disable:4786)
#include "DrawIt.h"

#include "DrawItDoc.h"
#include "DrawItView.h"

#include <algorithm>
#include <math.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CDrawItView

IMPLEMENT_DYNCREATE(CDrawItView, CView)

BEGIN_MESSAGE_MAP(CDrawItView, CView)
	//{{AFX_MSG_MAP(CDrawItView)
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_MOUSEMOVE()
	ON_COMMAND(ID_EDIT_ABUSE, OnEditAbuse)
	ON_COMMAND(ID_EDIT_ABUSE2, OnEditAbuse2)
	ON_COMMAND_RANGE(ID_INSERT_LINE, ID_INSERT_ELLIPSE, OnInsertCommand)
	ON_UPDATE_COMMAND_UI_RANGE(ID_INSERT_LINE, ID_INSERT_ELLIPSE, OnUpdateInsertCommand)
	//}}AFX_MSG_MAP
	// Standard printing commands
	ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
	ON_COMMAND(ID_VIEW_DRAWOBJECTS, OnViewDrawobjects)
	ON_UPDATE_COMMAND_UI(ID_VIEW_DRAWOBJECTS, OnUpdateViewDrawobjects)
//	ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CDrawItView construction/destruction

CDrawItView::CDrawItView()
: m_spec(0)
, m_bDrawItems(true), m_openMouseTransaction(0)
{
	// TODO: add construction code here

}

CDrawItView::~CDrawItView()
{
}

BOOL CDrawItView::PreCreateWindow(CREATESTRUCT& cs)
{
	// TODO: Modify the Window class or styles here by modifying
	//  the CREATESTRUCT cs

	return CView::PreCreateWindow(cs);
}

/////////////////////////////////////////////////////////////////////////////
// Helpers

class ViewApi : public DrawItAPI
{
public:
	ViewApi(CDrawItDoc::Points& p, CDrawItDoc::ObjectRecord* r) : points(p), rec(r) 
	{
		InitData();
	}

	virtual ~ViewApi() {}

	CDrawItDoc::Points& points;
	CDrawItDoc::ObjectRecord* rec;

	DRAWDATA data;

	// internal use
	void ChangeRecord(CDrawItDoc::ObjectRecord* r)
	{
		rec = r;
		InitData();
	}

	void InitData()
	{
		data.count_data = rec ? rec->second.size() : 0;
		data.data = rec ? &rec->second[0] : 0;
	}

	// api methods

	virtual unsigned long AddPoint(const POINT* where)
	{
		unsigned long id = points.size() + 1;
		while (points.find(id) != points.end()) id = rand() % ((unsigned long)-1);
		points[id] = *where;

		InitData();

		return id;
	}

	virtual void SetPoint(unsigned long id, const POINT* where)
	{
		points[id] = *where;
	}

	virtual const POINT* GetPoint(unsigned long id)
	{
		if (points.find(id) == points.end()) return NULL;
		return &points[id];
	}

	virtual void RemovePoint(unsigned long id)
	{
		CDrawItDoc::Points::iterator i = points.find(id);
		if (i == points.end()) return;

		points.erase(i);
	}

	virtual unsigned long AddData(unsigned long d)
	{
		rec->second.push_back(d);
		InitData();
		return rec->second.size() - 1;
	}

	virtual void SetData(unsigned long id, unsigned long d)
	{
		rec->second[id] = d;
	}

	virtual void ResizeData(unsigned int count)
	{
		rec->second.resize(count);
	}
};

struct DrawHelper
{
	DrawHelper(HDC dc, CDrawItDoc::Points& pts)
		: api(pts, NULL), hdc(dc)
	{
	}
	
	void operator()(CDrawItDoc::Objects::value_type& rec)
	{
		const CDrawItDoc::TypeCode& c = rec.second.first;
		const CDrawItDoc::ObjectData& d = rec.second.second;

		api.ChangeRecord(&rec.second);

		data.count_data = d.size();
		data.data = data.count_data ? const_cast<unsigned long*>(&d[0]) : 0;

		(*g_drawFunctions[c].draw)(&api, hdc, &data);
	}
	ViewApi			api;
	HDC				hdc;
	DRAWDATA		data;
};

/////////////////////////////////////////////////////////////////////////////
// CDrawItView drawing and input

void CDrawItView::OnDraw(CDC* pDC)
{
	if (!m_bDrawItems) return;

	CDrawItDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);

	if (pDC->IsPrinting()) {
		XFORM xform; ZeroMemory(&xform, sizeof(xform));
		xform.eDx = xform.eDy = 0.0;
		xform.eM11 = xform.eM22 = 5.0;
		xform.eM12 = xform.eM21 = 0.0;

		::SetGraphicsMode(pDC->m_hDC, GM_ADVANCED);
		::SetWorldTransform(pDC->m_hDC, &xform);
	}

	if (pDoc->data->m_objects.size() > 0) {
		DrawHelper helper(pDC->GetSafeHdc(), pDoc->data->m_points);
		std::for_each(pDoc->data->m_objects.begin(), pDoc->data->m_objects.end(), helper);
	}
}

/////////////////////////////////////////////////////////////////////////////
// CDrawItView printing

BOOL CDrawItView::OnPreparePrinting(CPrintInfo* pInfo)
{
	// default preparation
	return DoPreparePrinting(pInfo);
}

void CDrawItView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
	// TODO: add extra initialization before printing
}

void CDrawItView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
	// TODO: add cleanup after printing
}

/////////////////////////////////////////////////////////////////////////////
// CDrawItView diagnostics

#ifdef _DEBUG
void CDrawItView::AssertValid() const
{
	CView::AssertValid();
}

void CDrawItView::Dump(CDumpContext& dc) const
{
	CView::Dump(dc);
}

CDrawItDoc* CDrawItView::GetDocument() // non-debug version is inline
{
	ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CDrawItDoc)));
	return (CDrawItDoc*)m_pDocument;
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CDrawItView message handlers

void CDrawItView::OnLButtonDown(UINT nFlags, CPoint point) 
{
	Mm::SPACEID sid = GetDocument()->sid;
	if (Mm::GetOpenTransactionId(sid) == 0) {
		Mm::OpenTransaction(sid);
		m_openMouseTransaction = Mm::GetOpenTransactionId(sid);
	}

	CDrawItDoc::Objects& objects = GetDocument()->data->m_objects;
	CDrawItDoc::Points& points = GetDocument()->data->m_points;
	
	unsigned long id = objects.size() + 1;
	while (objects.find(id) != objects.end()) id = rand() % ((unsigned long)-1);
	
	objects[id] = CDrawItDoc::ObjectRecord();
	
	CDrawItDoc::ObjectRecord& r = objects[id];
	r.first = m_spec;

	ViewApi api(points, &r);
	(*g_drawFunctions[r.first].create)(&api, &point, &api.data);

	m_idCreating = id;

	Invalidate();

	CView::OnLButtonDown(nFlags, point);
}

void CDrawItView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	Mm::SPACEID sid = GetDocument()->sid;
	if (m_idCreating != 0 && Mm::GetOpenTransactionId(sid) != 0) {
		CDrawItDoc::Objects& objects = GetDocument()->data->m_objects;
		CDrawItDoc::Points& points = GetDocument()->data->m_points;

		CDrawItDoc::ObjectRecord& r = objects[m_idCreating];
		ViewApi api(points, &r);
		(*g_drawFunctions[r.first].complete)(&api, &api.data);
	}

	m_idCreating = 0;

	if (m_openMouseTransaction != 0 && Mm::GetOpenTransactionId(sid) == m_openMouseTransaction) {
		Mm::CommitTransaction(sid);
	}

	m_openMouseTransaction = 0;

	Invalidate();
	
	CView::OnLButtonUp(nFlags, point);
}

void CDrawItView::OnMouseMove(UINT nFlags, CPoint point) 
{
	Mm::SPACEID sid = GetDocument()->sid;
	if (Mm::GetOpenTransactionId(sid) != 0 && (nFlags & MK_LBUTTON) && m_idCreating != 0) {
		CDrawItDoc::Objects& objects = GetDocument()->data->m_objects;
		CDrawItDoc::Points& points = GetDocument()->data->m_points;

		CDrawItDoc::ObjectRecord& r = objects[m_idCreating];

		ViewApi api(points, &r);
		(*g_drawFunctions[r.first].create)(&api, &point, &api.data);

		Invalidate();
	}

	CView::OnMouseMove(nFlags, point);
}

void CDrawItView::OnEditAbuse() 
{
	Mm::SPACEID sid = GetDocument()->sid;

	CDrawItDoc::Objects& objects = GetDocument()->data->m_objects;
	CDrawItDoc::Points& points = GetDocument()->data->m_points;
	
	for (unsigned int i = 0; i < 1000; ++i)
	{
		Mm::AutoTransaction txn(sid);

		POINT p1, p2;
		p1.x = rand() % 1000;
		p1.y = rand() % 1000;
		p2.x = p1.x + rand() % 100;
		p2.y = p1.y + rand() % 100;
		
		unsigned long id = objects.size() + 1;
		while (objects.find(id) != objects.end()) id = rand() % ((unsigned long)-1);
		objects[id] = CDrawItDoc::ObjectRecord();
		
		CDrawItDoc::ObjectRecord& r = objects[id];
		r.first = (rand() % 5);

		ViewApi api(points, &r);
		(*g_drawFunctions[r.first].create)(&api, &p1, &api.data);
		(*g_drawFunctions[r.first].create)(&api, &p2, &api.data);
		(*g_drawFunctions[r.first].complete)(&api, &api.data);
	}

	Invalidate();
}

void CDrawItView::OnEditAbuse2() 
{
	Mm::SPACEID sid = GetDocument()->sid;

	CDrawItDoc::Objects& objects = GetDocument()->data->m_objects;
	CDrawItDoc::Points& points = GetDocument()->data->m_points;
	
	Mm::AutoTransaction txn(sid);

	CDrawItDoc::ObjectRecord rec;
	for (unsigned int i = 0; i < 1000; ++i)
	{
		POINT p1, p2;
		p1.x = rand() % 1000;
		p1.y = rand() % 1000;
		p2.x = p1.x + rand() % 100;
		p2.y = p1.y + rand() % 100;
		
		unsigned long id = objects.size() + 1;
		while (objects.find(id) != objects.end()) 
			id = rand() % ((unsigned long)-1);
		objects[id] = CDrawItDoc::ObjectRecord();
		
		CDrawItDoc::ObjectRecord& r = objects[id];
		r.first = (rand() % 5);

		ViewApi api(GetDocument()->data->m_points, &r);
		(*g_drawFunctions[r.first].create)(&api, &p1, &api.data);
		(*g_drawFunctions[r.first].create)(&api, &p2, &api.data);
		(*g_drawFunctions[r.first].complete)(&api, &api.data);
	}

	Invalidate();
}

void CDrawItView::OnInsertCommand(UINT id) 
{
	const CDrawItDoc::TypeCode spec = (CDrawItDoc::TypeCode)(id - ID_INSERT_LINE);
	m_spec = spec;
}

void CDrawItView::OnUpdateInsertCommand(CCmdUI* pCmdUI) 
{
	Mm::SPACEID sid = GetDocument()->sid;
	const CDrawItDoc::TypeCode spec = (CDrawItDoc::TypeCode)(pCmdUI->m_nID - ID_INSERT_LINE);
	pCmdUI->Enable(TRUE);
	pCmdUI->SetCheck(spec == m_spec ? TRUE : FALSE);

	CString msg; 
	msg.Format("%d points, %d shapes : %d bytes free in %d chunks", 
		GetDocument()->data->m_points.size(), 
		GetDocument()->data->m_objects.size(),
		Mm::FreeSpace(sid), Mm::FreeCount(sid));
	GetParentFrame()->SetMessageText(msg);
}

void CDrawItView::OnViewDrawobjects()
{
	m_bDrawItems = !m_bDrawItems;
	Invalidate();
}

void CDrawItView::OnUpdateViewDrawobjects(CCmdUI *pCmdUI)
{
	pCmdUI->SetCheck(m_bDrawItems ? 1 : 0);
}

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
United States United States
A compiler warns of bogasity, ignore it at your peril. Unless you've done the compiler's job yourself, don't criticize it.

Comments and Discussions