Click here to Skip to main content
15,886,788 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 289.8K   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.
// DrawFunc.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"
#include "DrawFunc.h"

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

#include "..\Common\Curve Simplification\DPHull.h" // for sketch simplification

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
					 )
{
    switch (ul_reason_for_call)
	{
		case DLL_PROCESS_ATTACH:
		case DLL_THREAD_ATTACH:
		case DLL_THREAD_DETACH:
		case DLL_PROCESS_DETACH:
			break;
    }
    return TRUE;
}

namespace DrawFunc {
	namespace Sketch {
		void Create(DrawItAPI* api, const POINT* mouse, const DRAWDATA* data)
		{
			switch (data->count_data) {
			case 0: {
				api->AddData(api->AddPoint(mouse));
				} break;

			default: {
				api->AddData(api->AddPoint(mouse));
				} break;
			};
		}

		struct DPPOINT {
			double x;
			double y;
		};

		void Complete(DrawItAPI* api, const DRAWDATA* data)
		{
			std::vector<DPPOINT> points;
			points.resize(data->count_data);

			for (unsigned int i = 0; i < points.size(); ++i) {
				const POINT* pt = api->GetPoint(data->data[i]);
				points[i].x = pt->x;
				points[i].y = pt->y;
			}

			CDPHull hull;
			hull.SetTol(5.0);
			hull.SetCurve(points.size(), (DP_POINT*)&points[0].x);
			hull.Approximate();
			
			hull.InjectDPInOriginal();
			points.resize(hull.GetDPNPoints());

			for (unsigned int i = 0; i < points.size(); ++i) {
				POINT p = { (LONG)points[i].x, (LONG)points[i].y };
				api->SetPoint(data->data[i], &p);
			}

			for (; i < data->count_data; ++i) {
				api->RemovePoint(data->data[i]);
			}

			api->ResizeData(points.size());
		}

		void Draw(DrawItAPI* api, HDC dc, const DRAWDATA* data)
		{
			switch (data->count_data) {
			default: {
					const POINT* pt = api->GetPoint(data->data[0]);
					::MoveToEx(dc, pt->x, pt->y, NULL);
					for (unsigned long i = 1; i < data->count_data; ++i) {
						const POINT* pt = api->GetPoint(data->data[i]);
						::LineTo(dc, pt->x, pt->y);

						const int o = 4;
						::MoveToEx(dc, pt->x + o, pt->y + o, NULL);
						::LineTo(dc, pt->x - o, pt->y - o);

						::MoveToEx(dc, pt->x + o, pt->y - o, NULL);
						::LineTo(dc, pt->x - o, pt->y + o);

						::MoveToEx(dc, pt->x, pt->y, NULL);
					}
				} break;

			case 1: {
					const POINT* pt = api->GetPoint(data->data[0]);
					::SetPixel(dc, pt->x, pt->y, RGB(0,0,0));
				} break;

			case 0:
				break;
			};
		}

		static CLIENTCALLBACKS callbacks = { &Create, &Complete, &Draw };
	};

	namespace Line {
		void Create(DrawItAPI* api, const POINT* mouse, const DRAWDATA* data)
		{
			switch (data->count_data) {
			case 0: {
				api->AddData(api->AddPoint(mouse));
				api->AddData(api->AddPoint(mouse));
				} break;

			case 2: {
				api->SetPoint(data->data[1], mouse);
				} break;

			default:
				break;
			};
		}

		void Complete(DrawItAPI* api, const DRAWDATA* data)
		{
		}

		void Draw(DrawItAPI* api, HDC dc, const DRAWDATA* data)
		{
			switch (data->count_data) {
			default: {
					const POINT* pt = api->GetPoint(data->data[0]);
					::MoveToEx(dc, pt->x, pt->y, NULL);
					for (unsigned long i = 1; i < data->count_data; ++i) {
						const POINT* pt = api->GetPoint(data->data[i]);
						::LineTo(dc, pt->x, pt->y);
					}
				} break;

			case 1: {
					const POINT* pt = api->GetPoint(data->data[0]);
					::SetPixel(dc, pt->x, pt->y, RGB(0,0,0));
				} break;

			case 0:
				break;
			};
		}

		static CLIENTCALLBACKS callbacks = { &Create, &Complete, &Draw };
	};

	namespace Spline {
		void Create(DrawItAPI* api, const POINT* mouse, const DRAWDATA* data)
		{
			switch (data->count_data) {
			case 0: {
				api->AddData(api->AddPoint(mouse));
				api->AddData(api->AddPoint(mouse));
				api->AddData(api->AddPoint(mouse));
				api->AddData(api->AddPoint(mouse));
				} break;

			case 4: {
				POINT start = *api->GetPoint(data->data[0]);
				POINT mid = { (mouse->x + start.x)/2, (mouse->y + start.y)/2 };
				POINT pt1 = { mid.x, mid.y + 50 };
				POINT pt2 = { mid.x, mid.y - 50 };

				api->SetPoint(data->data[1], &pt1);
				api->SetPoint(data->data[2], &pt2);
				api->SetPoint(data->data[3], mouse);
				} break;

			default:
				break;
			};
		}

		void Complete(DrawItAPI* api, const DRAWDATA* data)
		{
		}

		void Draw(DrawItAPI* api, HDC dc, const DRAWDATA* data)
		{
			const unsigned int remainder = data->count_data % 3;

			std::vector<POINT> spline; spline.reserve(100);
			for (unsigned long i = 0; i < data->count_data; ++i)
				spline.push_back(*api->GetPoint(data->data[i]));
			
			switch (remainder) {
			case 2: // need two more ("from" and "to" control points) so just make something up
				{
					std::vector<POINT>::iterator i = spline.end();
					spline.insert(--i, spline[data->count_data - 2]);
				}

			case 0: // need one more (just the "to" control point) so just duplicate the "to" point
				spline.push_back(spline.back());

			case 1: // just right
				::PolyBezier(dc, &spline[0], spline.size());
				break;
			};
		}

		static CLIENTCALLBACKS callbacks = { &Create, &Complete, &Draw };
	};

	namespace Rectangle {
		void Create(DrawItAPI* api, const POINT* mouse, const DRAWDATA* data)
		{
			switch (data->count_data) {
			case 0: {
				api->AddData(api->AddPoint(mouse));
				api->AddData(api->AddPoint(mouse));
				} break;

			case 2: {
				api->SetPoint(data->data[1], mouse);
				} break;

			default:
				break;
			};
		}

		void Complete(DrawItAPI* api, const DRAWDATA* data)
		{
		}

		void Draw(DrawItAPI* api, HDC dc, const DRAWDATA* data)
		{
			static const COLORREF color1 = RGB(255,128,128);
			static const COLORREF color2 = RGB(255,255,128);

			switch (data->count_data) {
			case 2: {
				const POINT* pt1 = api->GetPoint(data->data[0]);
				const POINT* pt2 = api->GetPoint(data->data[1]);

				TRIVERTEX vert[2];
				const unsigned short scale = 0xFFFF / 0xFF;

				vert[0].x      = pt1->x;
				vert[0].y      = pt1->y;
				vert[0].Red    = GetRValue(color1) * scale;
				vert[0].Green  = GetGValue(color1) * scale;
				vert[0].Blue   = GetBValue(color1) * scale;
				vert[0].Alpha  = 0xFFFF;

				vert[1].x      = pt2->x;
				vert[1].y      = pt2->y; 
				vert[1].Red    = GetRValue(color2) * scale;
				vert[1].Green  = GetGValue(color2) * scale;
				vert[1].Blue   = GetBValue(color2) * scale;
				vert[1].Alpha  = 0xFFFF;

				GRADIENT_RECT    gRect;
				gRect.UpperLeft  = 0;
				gRect.LowerRight = 1;

				::GradientFill(dc, vert, 2, &gRect, 1, GRADIENT_FILL_RECT_V);
				} break;

			default:
				break;
			};
		}

		static CLIENTCALLBACKS callbacks = { &Create, &Complete, &Draw };
	};

	namespace Circle {
		void Create(DrawItAPI* api, const POINT* mouse, const DRAWDATA* data)
		{
			switch (data->count_data) {
			case 0: {
				api->AddData(api->AddPoint(mouse));
				api->AddData(api->AddPoint(mouse));
				} break;

			case 2: {
				api->SetPoint(data->data[1], mouse);
				} break;

			default:
				break;
			};
		}

		void Complete(DrawItAPI* api, const DRAWDATA* data)
		{
		}

		void Draw(DrawItAPI* api, HDC dc, const DRAWDATA* data)
		{
			switch (data->count_data) {
			case 2:
				{
					const POINT* on1 = api->GetPoint(data->data[0]);
					const POINT* on2 = api->GetPoint(data->data[1]);
					const unsigned int size = static_cast<unsigned int>(sqrt((double)(on1->x - on2->x) * (on1->x - on2->x) 
						+ (on1->y - on2->y) * (on1->y - on2->y)) / 2);
					
					POINT center;
					center.x = (on1->x + on2->x) / 2; 
					center.y = (on1->y + on2->y) / 2;
					::Ellipse(dc, center.x - size, center.y - size, center.x + size, center.y + size);
				} break;

				default:
					break;
			};
		}

		static CLIENTCALLBACKS callbacks = { &Create, &Complete, &Draw };
	};

	namespace Ellipse {
		void Create(DrawItAPI* api, const POINT* mouse, const DRAWDATA* data)
		{
			switch (data->count_data) {
			case 0: {
				api->AddData(api->AddPoint(mouse));
				api->AddData(api->AddPoint(mouse));
				} break;

			case 2: {
				api->SetPoint(data->data[1], mouse);
				} break;

			default:
				break;
			};
		}

		void Complete(DrawItAPI* api, const DRAWDATA* data)
		{
			srand(time(0));
			if ((rand() % 100) == 0) { // crash randomly
				char* foo = NULL;
				foo[0]++;
			}
		}

		void Draw(DrawItAPI* api, HDC dc, const DRAWDATA* data)
		{
			switch (data->count_data) {
			case 2: {
				const POINT* pt1 = api->GetPoint(data->data[0]);
				const POINT* pt2 = api->GetPoint(data->data[1]);
				::Ellipse(dc, pt1->x, pt1->y, pt2->x, pt2->y);
				} break;

			default:
				break;
			};
		}

		static CLIENTCALLBACKS callbacks = { &Create, &Complete, &Draw };
	};
};  // namespace DrawFunc

DRAWFUNC_API bool DrawDllInit(PFNADDCLIENTCALLBACKS fnAdd)
{
	bool ok = true;
	
	unsigned int lineCode		= (*fnAdd)(&DrawFunc::Line::callbacks);
	unsigned int sketchCode		= (*fnAdd)(&DrawFunc::Sketch::callbacks);
//	unsigned int splineCode		= (*fnAdd)(&DrawFunc::Spline::callbacks);
	unsigned int rectangleCode	= (*fnAdd)(&DrawFunc::Rectangle::callbacks);
	unsigned int circleCode		= (*fnAdd)(&DrawFunc::Circle::callbacks);
	unsigned int ellipseCode	= (*fnAdd)(&DrawFunc::Ellipse::callbacks);

	return true;
}

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