/******************************Module*Header*******************************\
* Module Name: wndstuff.cpp
*
* This file contains all the code necessary for a simple GDI+ primitive
* test.
*
* Author: J. Andrew Goossen [andrewgo]
*
* Copyright (c) 1991-2000 Microsoft Corporation
*
\**************************************************************************/

#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <float.h>
#include <windows.h>
#include <objbase.h>
#include <mmsystem.h>
#include <gdiplus.h>

using namespace Gdiplus;

#include "wndstuff.h"

#include "../gpinit.inc"

#define HIT_DISTANCE 16
#define ABS(x) (((x) >= 0) ? (x) : -(x))
#define ROUND(x) ((INT) floor(x + 0.5f))

// We set the GDI transform to a 16x scaling transform, and the mode to
// GM_ADVANCED, so that we can get full fractional accuracy on the points
// that we feed to GDI:

#define GDI_FIXEDPOINT_SCALE 16

// State for tracking the primitive vertices and transform:

const INT PrimitivePointsMax = 64;
INT PrimitivePointsCount = 4;
PointF PrimitivePoints[PrimitivePointsMax] = { PointF(100, 100), 
                                               PointF(100, 400), 
                                               PointF(400, 400), 
                                               PointF(400, 100) };
Matrix *PrimitiveTransform;
Matrix *PrimitiveInverseTransform;
INT PrimitiveDragVertex = -1;
BOOL IsAddingPoints = FALSE;

// State for tracking the location of the transform overlay:

const REAL OverlayDimension = 100;
PointF OverlayPoints[3];            // 3 device-space representing overlay,
                                    //   where [1] is the elbow
PointF OverlayOffset;               // World-space coordinate of overlay elbow
INT OverlayDragVertex = -1;

// Miscellaneous state:

INT WindowWidth;
INT WindowHeight;

// Settings:

BOOL DoFill = FALSE;
BOOL DoDraw = TRUE;
BOOL DoAntialias = FALSE;
BOOL DoGammaCorrect = FALSE;
BOOL DoGdi = FALSE;
BOOL DoClipGrid = FALSE;
BOOL DoRandomTest = FALSE;
BOOL DoWindingFill = FALSE;
BOOL DoAnchors = TRUE;
BOOL DoSpine = FALSE;
BOOL DoWiden = FALSE;
BOOL DoCompound = FALSE;
BOOL DoTransformOverlay = FALSE;
BOOL DoScalingOnly = FALSE;
BOOL DoShape = FALSE;
BOOL DoBrushRect = FALSE;

REAL RenderMiterLimit = 10;
INT RenderWidth = 1;
INT RenderAlpha = 255;

LinearGradientBrush *LinearBrush;
GraphicsPath *PathGradientPath;

Brush *RenderBrush;
Pen *RenderPen;
Region *RenderRegion;

HRGN RenderHrgn;
HBRUSH RenderHbrush;
HPEN RenderHpen;

WORD MmWrapMode = MM_WRAP_TILE;
WORD MmBrushType = MM_BRUSH_SOLID;
WORD MmEndCap = MM_CAP_FLAT;
WORD MmJoin = MM_JOIN_MITER;
WORD MmDashStyle = MM_STYLE_SOLID;
WORD MmAlignment = MM_ALIGNMENT_CENTER;
WORD MmPrimitive = MM_POLYGON;

// Other useful globals:

HINSTANCE ghInstance;
HWND      ghwndMain;
HBRUSH    ghbrWhite;

//FARPROC glpfnEnterWidth;
//FARPROC glpfnEnterAlpha;
//FARPROC glpfnEnterPoints;
//FARPROC glpfnEnterTransform;

/***************************************************************************\
* Creates the GDI+ brush to be used.
*
\***************************************************************************/

VOID
CreateBrush_Gdiplus()
{
    WrapMode wrapMode;
    INT i;

    // Delete the old brush:

    delete RenderBrush;

    LinearBrush = NULL;
    RenderBrush = NULL;

    // Create the new one:

    Bitmap bitmap(L"winnt256.bmp");

    switch (MmWrapMode)
    {
    case MM_WRAP_TILE: wrapMode = WrapModeTile; break;
    case MM_WRAP_CLAMP: wrapMode = WrapModeClamp; break;
    case MM_WRAP_FLIPX: wrapMode = WrapModeTileFlipX; break;
    case MM_WRAP_FLIPY: wrapMode = WrapModeTileFlipY; break;
    case MM_WRAP_FLIPXY: wrapMode = WrapModeTileFlipXY; break;
    }

    switch (MmBrushType)
    {
    case MM_BRUSH_SOLID:
        RenderBrush = new SolidBrush(Color(128, 128, 128));
        break;

    case MM_BRUSH_TEXTURE:
        RenderBrush = new TextureBrush(&bitmap, wrapMode);
        break;

    case MM_BRUSH_TEXTURE_32x32:
        {
            Bitmap texture(32, 32, PixelFormat32bppARGB);
            Graphics g(&texture);
            g.DrawImage(&bitmap, Rect(0, 0, 32, 32));
    
            TextureBrush *brush = new TextureBrush(&texture, wrapMode);

            // Set a translate:

            Matrix matrix(1, 0, 0, 1, 100, 100);
            brush->SetTransform(&matrix);

            RenderBrush = brush;
        }
        break;

    case MM_BRUSH_TEXTURE_1x1:
        {
            SolidBrush solidBrush(Color::Green);
            Bitmap texture(1, 1, PixelFormat32bppARGB);
            Graphics g(&texture);
            g.FillRectangle(&solidBrush, 0, 0, 1, 1);

            TextureBrush *brush = new TextureBrush(&texture, wrapMode);

            // Set a translate:

            Matrix matrix(1, 0, 0, 1, 100, 100);
            brush->SetTransform(&matrix);

            RenderBrush = brush;
        }
        break;

    case MM_BRUSH_LINEAR:
        {
            LinearGradientBrush *brush = new LinearGradientBrush(
                Point(100, 100), 
                Point(100, 300),
                Color::Red, 
                Color::Black
            );
            brush->SetWrapMode(wrapMode);
    
            RenderBrush = brush;
            LinearBrush = brush;
        }
        break;

    case MM_BRUSH_PATHGRADIENT:
        {
            PathGradientBrush *brush;
            INT count;

            if (PathGradientPath != NULL)
            {
                brush = new PathGradientBrush(PathGradientPath);
                count = PathGradientPath->GetPointCount();
            }
            else
            {
                // Substitute a default path for now:

                PointF points[] = { PointF(100, 100), PointF(100, 300),
                                    PointF(300, 300), PointF(30, 100) };

                brush = new PathGradientBrush(points, 4);
                count = 4;
            }

            Color *colors = new Color[count];

            for (i = 0; i < count; i += 2)
            {
                colors[i] = Color::Green;
                colors[i+1] = Color::Red;
            }
            brush->SetSurroundColors(colors, &count);

            delete [] colors;
    
            brush->SetCenterPoint(OverlayOffset);
            brush->SetCenterColor(Color::Black);

            RenderBrush = brush;
        }
        break;
    }
}

/***************************************************************************\
* Creates the GDI brush to be used.
*
\***************************************************************************/

VOID
CreateBrush_Gdi()
{
    DeleteObject(RenderHbrush);

    RenderHbrush = CreateSolidBrush(RGB(128, 128, 128));
}

/***************************************************************************\
* Creates the GDI and GDI+ brushes to be used.
*
\***************************************************************************/

VOID
CreateBrushes()
{
    CreateBrush_Gdiplus();
    CreateBrush_Gdi();
}

/***************************************************************************\
* Creates the GDI+ pen to be used.
*
\***************************************************************************/

VOID
CreatePen_Gdiplus()
{
    DashCap dashCap;
    LineCap lineCap;
    LineJoin lineJoin;
    DashStyle dashStyle;
    PenAlignment alignment;

    delete RenderPen;

    RenderPen = new Pen(Color((BYTE) RenderAlpha, 255, 0, 0), (REAL) RenderWidth);

    switch (MmEndCap)
    {
    case MM_CAP_ROUND: lineCap = LineCapRound; dashCap = DashCapRound; break;
    case MM_CAP_SQUARE: lineCap = LineCapSquare; dashCap = DashCapFlat; break;
    case MM_CAP_FLAT: lineCap = LineCapFlat; dashCap = DashCapFlat; break;
    case MM_CAP_TRIANGLE: lineCap = LineCapTriangle; dashCap = DashCapTriangle; break;
    }
    RenderPen->SetEndCap(lineCap);
    RenderPen->SetStartCap(lineCap);
    RenderPen->SetDashCap(dashCap);

    switch (MmJoin)
    {
    case MM_JOIN_ROUND: lineJoin = LineJoinRound; break;
    case MM_JOIN_BEVEL: lineJoin = LineJoinBevel; break;
    case MM_JOIN_MITER: lineJoin = LineJoinMiter; break;
    }
    RenderPen->SetLineJoin(lineJoin);

    switch (MmDashStyle)
    {
    case MM_STYLE_SOLID: dashStyle = DashStyleSolid; break;
    case MM_STYLE_DASH: dashStyle = DashStyleDash; break;
    case MM_STYLE_DOT: dashStyle = DashStyleDot; break;
    case MM_STYLE_DASHDOT: dashStyle = DashStyleDashDot; break;
    case MM_STYLE_DASHDOTDOT: dashStyle = DashStyleDashDotDot; break;
    }
    RenderPen->SetDashStyle(dashStyle);

    switch (MmAlignment)
    {
    case MM_ALIGNMENT_CENTER: alignment = PenAlignmentCenter; break;
    case MM_ALIGNMENT_INSET: alignment = PenAlignmentInset; break;
    }
    RenderPen->SetAlignment(alignment);

    RenderPen->SetMiterLimit(RenderMiterLimit);

    // We should add a 'compound array' UI to make this more flexible.
    // But for now, we only ever create one type of compound line:

    if (DoCompound)
    {
        REAL compoundArray[] = { 0.0f, 0.2f, 0.8f, 1.0f };
        RenderPen->SetCompoundArray(compoundArray, 4);
    }
}

/***************************************************************************\
* Creates the GDI pen to be used.
*
\***************************************************************************/

VOID
CreatePen_Gdi()
{
    DWORD lineCap;
    DWORD lineJoin;
    DWORD dashStyle;
    LOGBRUSH logBrush;

    DeleteObject(RenderHpen);

    switch (MmEndCap)
    {
    case MM_CAP_ROUND: lineCap = PS_ENDCAP_ROUND; break;
    case MM_CAP_SQUARE: lineCap = PS_ENDCAP_SQUARE; break;
    case MM_CAP_FLAT: lineCap = PS_ENDCAP_FLAT; break;
    case MM_CAP_TRIANGLE: lineCap = PS_ENDCAP_SQUARE; break;    // No equivalent
    }

    switch (MmJoin)
    {
    case MM_JOIN_ROUND: lineJoin = PS_JOIN_ROUND; break;
    case MM_JOIN_BEVEL: lineJoin = PS_JOIN_BEVEL; break;
    case MM_JOIN_MITER: lineJoin = PS_JOIN_MITER; break;
    }

    switch (MmDashStyle)
    {
    case MM_STYLE_SOLID: dashStyle = PS_SOLID; break;
    case MM_STYLE_DASH: dashStyle = PS_DASH; break;
    case MM_STYLE_DOT: dashStyle = PS_DOT; break;
    case MM_STYLE_DASHDOT: dashStyle = PS_DASHDOT; break;
    case MM_STYLE_DASHDOTDOT: dashStyle = PS_DASHDOTDOT; break;
    }

    logBrush.lbStyle = BS_SOLID;
    logBrush.lbColor = RGB(255, 0, 0);
    logBrush.lbHatch = 0;

    RenderHpen = ExtCreatePen(lineCap | lineJoin | dashStyle | PS_GEOMETRIC,
                              GDI_FIXEDPOINT_SCALE * RenderWidth,
                              &logBrush,
                              0,
                              NULL);
}

/***************************************************************************\
* Creates the GDI+ and GDI pens to be used.
*
\***************************************************************************/

VOID
CreatePens()
{
    CreatePen_Gdiplus();
    CreatePen_Gdi();
}

/***************************************************************************\
* Creates the GDI+ clip region to be used.
*
\***************************************************************************/

VOID
CreateRegion_Gdiplus()
{
    INT x;
    INT y;

    delete RenderRegion;
    
    RenderRegion = new Region();
    
    for (x = 0; x < WindowWidth; x += 128)
    {
        for (y = 0; y < WindowHeight; y += 128)
        {
            RenderRegion->Exclude(Rect(x + 64, y, 64, 64));
            RenderRegion->Exclude(Rect(x, y + 64, 64, 64));
        }
    }
}

/***************************************************************************\
* Creates the GDI clip region to be used.
*
\***************************************************************************/

VOID
CreateRegion_Gdi()
{
    INT x;
    INT y;
    HRGN hrgn;

    DeleteObject(RenderHrgn);

    RenderHrgn = CreateRectRgn(0, 0, WindowWidth, WindowHeight);

    hrgn = CreateRectRgn(0, 0, 0, 0);

    for (x = 0; x < WindowWidth; x += 128)
    {
        for (y = 0; y < WindowHeight; y += 128)
        {
            SetRectRgn(hrgn, x + 64, y, x + 128, y + 64);
            CombineRgn(RenderHrgn, RenderHrgn, hrgn, RGN_DIFF);

            SetRectRgn(hrgn, x, y + 64, x + 64, y + 128);
            CombineRgn(RenderHrgn, RenderHrgn, hrgn, RGN_DIFF);
        }
    }

    DeleteObject(hrgn);
}

/***************************************************************************\
* Free all our global objects.
*
\***************************************************************************/

VOID
DeleteObjects_Gdiplus()
{
    delete RenderRegion;
    delete RenderBrush;
    delete RenderPen;

    delete PrimitiveTransform;
    delete PrimitiveInverseTransform;
}

/***************************************************************************\
* Free all our global objects.
*
\***************************************************************************/

VOID
DeleteObjects_Gdi()
{
    DeleteObject(RenderHrgn);
    DeleteObject(RenderHbrush);
    DeleteObject(RenderHpen);
}

/***************************************************************************\
* Draw the control points
*
\***************************************************************************/

VOID
DrawAnchors(
    Graphics *g,
    PointF* points,
    INT count
    )
{
    SolidBrush blueBrush(Color(150, 128, 128, 128));

    for (; count != 0; count--, points++)
    {
        PointF point(*points);

        PrimitiveTransform->TransformPoints(&point, 1);

        g->FillRectangle(&blueBrush, RectF(point.X - 2, point.Y - 2, 5, 5));
    }
}

/***************************************************************************\
* DrawTransformOverlay
*
\***************************************************************************/

VOID
DrawTransformOverlay(
    Graphics *g
    )
{
    Pen pen(Color::Purple, 1);
    SolidBrush brush(Color::Purple);

    g->DrawLine(&pen, OverlayPoints[1].X, OverlayPoints[1].Y,
                      OverlayPoints[0].X, OverlayPoints[0].Y);
    g->DrawLine(&pen, OverlayPoints[1].X, OverlayPoints[1].Y,
                      OverlayPoints[2].X, OverlayPoints[2].Y);

    g->FillRectangle(&brush, RectF(OverlayPoints[0].X - 2, 
                                   OverlayPoints[0].Y - 2, 
                                   5, 
                                   5));

    g->FillRectangle(&brush, RectF(OverlayPoints[2].X - 2, 
                                   OverlayPoints[2].Y - 2, 
                                   5, 
                                   5));
}

/***************************************************************************\
* Render_Gdiplus
*
\***************************************************************************/

INT
Render_Gdiplus(
    Graphics *g,
    PointF *points,
    INT count
    )
{
    INT i;
    INT pointsUsed;

//    if (DoBrushRect)
//    {
//        LinearBrush->SetLinearPoints(points[0], points[1]);
//    }

    RectF rect(points[0].X, points[0].Y, 
               points[1].X - points[0].X, points[1].Y - points[0].Y);

    Pen spinePen(Color(0, 128, 0), 0);
    GraphicsPath shapePath;

    switch (MmPrimitive)
    {
        case MM_POLYGON:
        {
            shapePath.AddPolygon(points, count);
            if (!DoShape)
            {
                if (DoFill)
                    g->FillPolygon(RenderBrush, points, count, (DoWindingFill) 
                                                        ? FillModeWinding
                                                        : FillModeAlternate);
                if (DoDraw)
                    g->DrawPolygon(RenderPen, points, count);
                if (DoSpine)
                    g->DrawPolygon(&spinePen, points, count);
            }

            pointsUsed = count;
            break;
        }

        case MM_LINES:
        {
            shapePath.AddLines(points, count);
            if (!DoShape)
            {
                if (DoDraw)
                    g->DrawLines(RenderPen, points, count);
                if (DoSpine)
                    g->DrawLines(&spinePen, points, count);
            }

            pointsUsed = count;
            break;
        }
    
        case MM_BEZIER:
        {
            GraphicsPath path;
            path.AddBeziers(points, count);

            shapePath.AddPath(&path, FALSE);
            if (!DoShape)
            {
                if (DoFill)
                    g->FillPath(RenderBrush, &path);
                if (DoDraw)
                    g->DrawPath(RenderPen, &path);
                if (DoSpine)
                    g->DrawPath(&spinePen, &path);
            }

            pointsUsed = count;
            break;
        }
    
        case MM_RECTANGLE:
        {
            shapePath.AddRectangle(rect);
            if (!DoShape)
            {
                if (DoFill)
                    g->FillRectangle(RenderBrush, rect);
                if (DoDraw)
                    g->DrawRectangle(RenderPen, rect);
                if (DoSpine)
                    g->DrawRectangle(&spinePen, rect);
            }

            pointsUsed = 2;
            break;
        }
    
        case MM_ELLIPSE:
        {
            shapePath.AddEllipse(rect);
            if (!DoShape)
            {
                if (DoFill)
                    g->FillEllipse(RenderBrush, rect);  
                if (DoDraw)
                    g->DrawEllipse(RenderPen, rect);
                if (DoSpine)
                    g->DrawEllipse(&spinePen, rect);
            }

            pointsUsed = 2;
            break;
        }
    
        case MM_TEXTPATH:
        {
            WCHAR string[] = L"GDI+ Rules!";
            GraphicsPath path((DoWindingFill) ? FillModeWinding
                                              : FillModeAlternate);

            FontFamily family(L"Times New Roman");
            PointF origin(points[0].X, points[0].Y);
    
            path.AddString(
                string, 
                wcslen(string), 
                &family, 
                0, 
                200, 
                origin, 
                NULL
            );

            shapePath.AddPath(&path, FALSE);
            if (!DoShape)
            {
                if (DoFill)
                    g->FillPath(RenderBrush, &path);
                if (DoDraw)
                    g->DrawPath(RenderPen, &path);
                if (DoSpine)
                    g->DrawPath(&spinePen, &path);
            }

            pointsUsed = 1;
            break;
        }
    }

    if (DoShape)
    {
        // Recreate the path to be use for the path-gradient brush, 
        // using the new shape data:

        delete PathGradientPath;
        PathGradientPath = shapePath.Clone();

        // Recreate the brush and do a complete fill of the window using
        // the specified brush:

        CreateBrushes();

        if (DoFill)
            g->FillRectangle(RenderBrush, -262144, -262144, 524288, 524288);
        if (DoDraw)
            g->DrawPath(RenderPen, &shapePath);
        if (DoSpine)
            g->DrawPath(&spinePen, &shapePath);
    }

    if (DoWiden)
    {
        Pen widenPen(Color::Black, 0);

        shapePath.Widen(RenderPen, NULL);

        g->DrawPath(&widenPen, &shapePath);
    }

    return(pointsUsed);
}

/***************************************************************************\
* Render_Gdi
*
\***************************************************************************/

INT
Render_Gdi(
    HDC hdc,
    PointF *primitivePoints,
    INT count
    )
{
    BOOL drawSpine;
    HGDIOBJ oldPen;
    HGDIOBJ oldBrush;
    HPEN hpenSpine;
    INT pointsUsed;
    POINT points[PrimitivePointsMax];
    INT i;

    // Convert to integer, the preferred GDI format.  Remember that we've
    // set the transform to scale down by 16, so we have to multiply by
    // 16 here.  We've done this so that we can specify 28.4 directly to
    // GDI:

    for (i = 0; i < count; i++)
    {
        points[i].x = ROUND(GDI_FIXEDPOINT_SCALE * primitivePoints[i].X);
        points[i].y = ROUND(GDI_FIXEDPOINT_SCALE * primitivePoints[i].Y);
    }

    hpenSpine = CreatePen(PS_SOLID, 0, RGB(0, 128, 0));

    for (drawSpine = FALSE; drawSpine != TRUE; drawSpine = TRUE)
    {
        if (drawSpine)
        {
            oldPen = SelectObject(hdc, hpenSpine);
            oldBrush = SelectObject(hdc, GetStockObject(NULL_BRUSH));
        }
        else
        {
            oldPen = SelectObject(hdc,                                             
                    (DoDraw) ? RenderHpen : GetStockObject(NULL_PEN));     
            oldBrush = SelectObject(hdc,                                           
                    (DoFill) ? RenderHbrush : GetStockObject(NULL_BRUSH)); 
        }

        switch (MmPrimitive)
        {
            case MM_POLYGON:
            {
                Polygon(hdc, points, count);

                pointsUsed = count;
                break;
            }

            case MM_LINES:
            {
                Polyline(hdc, points, count);

                pointsUsed = count;
                break;
            }
        
            case MM_BEZIER:
            {
                // Don't use StrokeAndFillPath because GDI would close the
                // stroke:

                BeginPath(hdc);
                PolyBezier(hdc, points, count);
                EndPath(hdc);
                FillPath(hdc);

                BeginPath(hdc);
                PolyBezier(hdc, points, count);
                EndPath(hdc);
                StrokePath(hdc);

                pointsUsed = count;
                break;
            }
        
            case MM_RECTANGLE:
            {
                Rectangle(hdc, points[0].x, points[0].y, points[1].x, points[1].y);

                pointsUsed = 2;
                break;
            }
        
            case MM_ELLIPSE:
            {
                Ellipse(hdc, points[0].x, points[0].y, points[1].x, points[1].y);

                pointsUsed = 2;
                break;
            }
        
            case MM_TEXTPATH:
            {
                LOGFONT logFont;
                memset(&logFont, 0, sizeof(logFont));

                // Don't forget to multiply the height by 16, because
                // we're using a scaling transform with GDI so that we
                // can spit out 28.4 coordinates:

                logFont.lfHeight = - GDI_FIXEDPOINT_SCALE * 200;   
                strcpy(logFont.lfFaceName, "Times New Roman");

                SetBkMode(hdc, TRANSPARENT);

                HFONT font = CreateFontIndirect(&logFont);
                HGDIOBJ oldFont = SelectObject(hdc, font);
    
                WCHAR string[] = L"GDI+ Rules!";
                BeginPath(hdc);
                ExtTextOutW(hdc, points[0].x, points[0].y, 0, NULL, string, 
                            wcslen(string), NULL);
                EndPath(hdc);
                StrokeAndFillPath(hdc);

                SelectObject(hdc, oldFont);
                DeleteObject(font);

                pointsUsed = 1;
            }
        }

        SelectObject(hdc, oldBrush);
        SelectObject(hdc, oldPen);
    }

    DeleteObject(hpenSpine);

    return(pointsUsed);
}

/***************************************************************************\
* PrepareContext_Gdiplus
*
\***************************************************************************/

VOID
PrepareContext_Gdiplus(
    Graphics *g
    )
{
    g->SetSmoothingMode((DoAntialias) ? SmoothingModeAntiAlias
                                      : SmoothingModeNone);
    
    g->SetCompositingQuality((DoGammaCorrect) ? 
        CompositingQualityGammaCorrected : CompositingQualityAssumeLinear);

    if (DoClipGrid)
    {
        g->SetClip(RenderRegion);
    }

    g->SetTransform(PrimitiveTransform);
}

/***************************************************************************\
* PrepareContext_Gdi
*
\***************************************************************************/

VOID
PrepareContext_Gdi(
    HDC hdc
    )
{
    REAL m[6];
    XFORM xform;

    SetMiterLimit(hdc, RenderMiterLimit, NULL);

    SetPolyFillMode(hdc, (DoWindingFill) ? WINDING : ALTERNATE);

    if (DoClipGrid)
    {
        SelectClipRgn(hdc, RenderHrgn);
    }

    // Setup the transform:

    PrimitiveTransform->GetElements(m);

    // Scale the transform down by 16 so that we can give GDI 28.4 
    // coordinates directly as integers:

    xform.eM11 = m[0] / GDI_FIXEDPOINT_SCALE;
    xform.eM12 = m[1] / GDI_FIXEDPOINT_SCALE;
    xform.eM21 = m[2] / GDI_FIXEDPOINT_SCALE;
    xform.eM22 = m[3] / GDI_FIXEDPOINT_SCALE;
    xform.eDx = m[4];
    xform.eDy = m[5];

    SetGraphicsMode(hdc, GM_ADVANCED);

    SetWorldTransform(hdc, &xform);
}

/***************************************************************************\
* GenerateRandomPoints
*
\***************************************************************************/

INT
GenerateRandomPoints(
    PointF *randomPoints,
    INT maxPoints
    )
{
    INT randomPointsCount;
    INT i;

    // Make 1 in 32 have lotsa randomPoints:

    if ((rand() & 31) == 0)
    {
        randomPointsCount = rand() & 511;
    }
    else
    {
        randomPointsCount = (rand() & 7) + 1;
    }

    randomPointsCount = min(randomPointsCount, maxPoints);

    // !!! Need to randomize

    switch (rand() & 3)
    {
    case 0:     // Trivially clipped
        for (i = 0; i < randomPointsCount; i++)
        {
            randomPoints[i].X = (rand() % (16 * WindowWidth * 16)) / 16.0f;
            randomPoints[i].Y = (rand() % (16 * WindowHeight)) / 16.0f;
        }
        break;

    case 1:     // Really small
        for (i = 0; i < randomPointsCount; i++)
        {
            randomPoints[i].X = (rand() & 127) / 16.0f + 32;
            randomPoints[i].Y = (rand() & 127) / 16.0f + 32;
        }
        break;

    default:     // Big space, with at least one point inside window:
        randomPoints[0].X = (rand() % (16 * WindowWidth)) / 16.0f;
        randomPoints[0].Y = (rand() % (16 * WindowHeight)) / 16.0f;

        if (0)
        {
            for (i = 1; i < randomPointsCount; i++)
            {
                // Once in a while, make the points REALLY REALLY big:

                randomPoints[i].X = (REAL) (rand() * rand() * rand());
                randomPoints[i].Y = (REAL) (rand() * rand() * rand());
            }
        }
        else
        {
            for (i = 1; i < randomPointsCount; i++)
            {
                randomPoints[i].X = (rand() % 1000000 - 500000) / 16.0f;
                randomPoints[i].Y = (rand() % 1000000 - 500000) / 16.0f;
            }
        }
        break;
    }

    return(randomPointsCount);
}

/***************************************************************************\
* Draw
*
\***************************************************************************/

VOID
Draw(
    HDC hdc,
    BOOL doTime = FALSE
    )
{
    CHAR stringBuffer[200];
    LONGLONG startCounter;
    LONGLONG endCounter;
    LONGLONG counterFrequency;
    INT pointsUsed;
    INT repetitions = (doTime) ? 10 : 1;
    INT i;

    // Clear the window:

    HGDIOBJ hbrush = GetStockObject(WHITE_BRUSH);
    HGDIOBJ holdBrush = SelectObject(hdc, hbrush);
    PatBlt(hdc, -10000, -10000, 20000, 20000, PATCOPY);
    SelectObject(hdc, holdBrush);
    DeleteObject(hbrush);

    QueryPerformanceCounter((LARGE_INTEGER*) &startCounter);

    // Draw the stuff:

    if (DoGdi)
    {
        SaveDC(hdc);

        PrepareContext_Gdi(hdc);

        for (i = 0; i < repetitions; i++)
        {
            pointsUsed = Render_Gdi(hdc, PrimitivePoints, PrimitivePointsCount);
        }

        RestoreDC(hdc, -1);
    }
    else
    {
        Graphics g(hdc);
        PrepareContext_Gdiplus(&g);

        if (!DoRandomTest)
        {
            for (i = 0; i < repetitions; i++)
            {
                pointsUsed = Render_Gdiplus(&g, PrimitivePoints, PrimitivePointsCount);
            }
        }
        else
        {
            PointF points[512];
            INT count;

            // To get faster 'test' rendering (by avoiding clears between
            // successive tests), always draw in a batch of '20':

            for (i = 0; i < 20; i++)
            {
                count = GenerateRandomPoints(points, 512);
                pointsUsed = Render_Gdiplus(&g, points, count);
            }
        }
    }

    // Display the time:

    QueryPerformanceCounter((LARGE_INTEGER*) &endCounter);
    QueryPerformanceFrequency((LARGE_INTEGER*) &counterFrequency);

    float seconds = (float)(endCounter - startCounter) / counterFrequency;
    INT milliseconds = (INT) (seconds * 1000 + 0.5f);

    if (doTime)
    {
        sprintf(stringBuffer, "%li repetitions: %li ms", repetitions, milliseconds);
    }
    else
    {
        sprintf(stringBuffer, "Rasterization time: %li ms", milliseconds);
    }

    SetBkMode(hdc, TRANSPARENT);
    ExtTextOut(hdc, 0, 0, 0, NULL, stringBuffer, strlen(stringBuffer), NULL);

    if (!DoRandomTest)
    {
        // Now that we're out of the timing loop, draw our control points:
    
        Graphics g(hdc);

        if (DoAnchors)
        {
            DrawAnchors(&g, PrimitivePoints, pointsUsed);
        }
    
        if (DoTransformOverlay)
        {
            DrawTransformOverlay(&g);
        }
    }
}

/***************************************************************************\
* EnterWidth
*
* Dialog for entering pen width.
\***************************************************************************/

INT_PTR EnterWidth(
    HWND    hDlg,
    UINT    message,
    WPARAM  wParam,
    LPARAM  lParam)
{
    BOOL    bTrans;

    switch (message)
    {
    case WM_INITDIALOG:
        SetDlgItemInt(hDlg, IDD_WIDTH, RenderWidth, TRUE);
        return(TRUE);

    case WM_COMMAND:
        if (wParam == IDD_OK)
        {
            RenderWidth = GetDlgItemInt(hDlg, IDD_WIDTH, &bTrans, TRUE);
            EndDialog(hDlg, wParam);
            InvalidateRect(ghwndMain, NULL, TRUE);
        }
        break;

    case WM_SETFOCUS:
        SetFocus(GetDlgItem(hDlg, IDD_WIDTH));
        return(FALSE);

    default:
        return(FALSE);
    }

    return(TRUE);
}

/***************************************************************************\
* EnterAlpha
*
* Dialog for entering pen alpha.
\***************************************************************************/

INT_PTR EnterAlpha(
    HWND    hDlg,
    UINT    message,
    WPARAM  wParam,
    LPARAM  lParam)
{
    BOOL    bTrans;

    switch (message)
    {
    case WM_INITDIALOG:
        SetDlgItemInt(hDlg, IDD_ALPHA, RenderAlpha, TRUE);
        return(TRUE);

    case WM_COMMAND:
        if (wParam == IDD_OK)
        {
            RenderAlpha = GetDlgItemInt(hDlg, IDD_ALPHA, &bTrans, TRUE);
            EndDialog(hDlg, wParam);
            InvalidateRect(ghwndMain, NULL, TRUE);
        }
        break;

    case WM_SETFOCUS:
        SetFocus(GetDlgItem(hDlg, IDD_ALPHA));
        return(FALSE);

    default:
        return(FALSE);
    }

    return(TRUE);
}

/***************************************************************************\
* EnterPoints
*
* Dialog for entering points.
\***************************************************************************/

INT_PTR EnterPoints(
    HWND    hDlg,
    UINT    message,
    WPARAM  wParam,
    LPARAM  lParam
    )
{
    BOOL    bTrans;
    INT     i;

    switch (message)
    {
    case WM_INITDIALOG:
        SetDlgItemInt(hDlg, IDD_POINT1X, ROUND(PrimitivePoints[0].X), TRUE);
        SetDlgItemInt(hDlg, IDD_POINT1Y, ROUND(PrimitivePoints[0].Y), TRUE);
        SetDlgItemInt(hDlg, IDD_POINT2X, ROUND(PrimitivePoints[1].X), TRUE);
        SetDlgItemInt(hDlg, IDD_POINT2Y, ROUND(PrimitivePoints[1].Y), TRUE);
        SetDlgItemInt(hDlg, IDD_POINT3X, ROUND(PrimitivePoints[2].X), TRUE);
        SetDlgItemInt(hDlg, IDD_POINT3Y, ROUND(PrimitivePoints[2].Y), TRUE);
        SetDlgItemInt(hDlg, IDD_POINT4X, ROUND(PrimitivePoints[3].X), TRUE);
        SetDlgItemInt(hDlg, IDD_POINT4Y, ROUND(PrimitivePoints[3].Y), TRUE);
        return(TRUE);

    case WM_COMMAND:
        if (wParam == IDD_OK)
        {
            PrimitivePoints[0].X = (REAL) (INT) GetDlgItemInt(hDlg, IDD_POINT1X, &bTrans, TRUE);
            PrimitivePoints[0].Y = (REAL) (INT) GetDlgItemInt(hDlg, IDD_POINT1Y, &bTrans, TRUE);
            PrimitivePoints[1].X = (REAL) (INT) GetDlgItemInt(hDlg, IDD_POINT2X, &bTrans, TRUE);
            PrimitivePoints[1].Y = (REAL) (INT) GetDlgItemInt(hDlg, IDD_POINT2Y, &bTrans, TRUE);
            PrimitivePoints[2].X = (REAL) (INT) GetDlgItemInt(hDlg, IDD_POINT3X, &bTrans, TRUE);
            PrimitivePoints[2].Y = (REAL) (INT) GetDlgItemInt(hDlg, IDD_POINT3Y, &bTrans, TRUE);
            PrimitivePoints[3].X = (REAL) (INT) GetDlgItemInt(hDlg, IDD_POINT4X, &bTrans, TRUE);
            PrimitivePoints[3].Y = (REAL) (INT) GetDlgItemInt(hDlg, IDD_POINT4Y, &bTrans, TRUE);

            EndDialog(hDlg, wParam);
            InvalidateRect(ghwndMain, NULL, TRUE);
        }
        break;

    case WM_SETFOCUS:
        SetFocus(GetDlgItem(hDlg, IDD_POINT1X));
        return(FALSE);

    default:
        return(FALSE);
    }

    return(TRUE);
}

/***************************************************************************\
* EnterTransform
*
* Dialog for entering arbitrary transforms.
\***************************************************************************/

INT_PTR EnterTransform(
    HWND    hDlg,
    WORD    message,
    WPARAM  wParam,
    LONG    lParam)
{
    BOOL    bTrans;
    REAL    m[6];

    PrimitiveTransform->GetElements(m);

    switch (message)
    {
    case WM_INITDIALOG:
        SetDlgItemInt(hDlg, IDD_M11, ROUND(m[0] * 1000.0f), TRUE);
        SetDlgItemInt(hDlg, IDD_M12, ROUND(m[1] * 1000.0f), TRUE);
        SetDlgItemInt(hDlg, IDD_M21, ROUND(m[2] * 1000.0f), TRUE);
        SetDlgItemInt(hDlg, IDD_M22, ROUND(m[3] * 1000.0f), TRUE);
        SetDlgItemInt(hDlg, IDD_M31, ROUND(m[4] * 1000.0f), TRUE);
        SetDlgItemInt(hDlg, IDD_M32, ROUND(m[5] * 1000.0f), TRUE);
        return(TRUE);

    case WM_COMMAND:
        if (wParam == IDD_OK)
        {
            m[0] = ((INT) GetDlgItemInt(hDlg, IDD_M11, &bTrans, TRUE)) / 1000.0f;
            m[1] = ((INT) GetDlgItemInt(hDlg, IDD_M12, &bTrans, TRUE)) / 1000.0f;
            m[2] = ((INT) GetDlgItemInt(hDlg, IDD_M21, &bTrans, TRUE)) / 1000.0f;
            m[3] = ((INT) GetDlgItemInt(hDlg, IDD_M22, &bTrans, TRUE)) / 1000.0f;
            m[4] = ((INT) GetDlgItemInt(hDlg, IDD_M31, &bTrans, TRUE)) / 1000.0f;
            m[5] = ((INT) GetDlgItemInt(hDlg, IDD_M32, &bTrans, TRUE)) / 1000.0f;

            PrimitiveTransform->SetElements(m[0], m[1], m[2], m[3], m[4], m[5]);
            PrimitiveInverseTransform->SetElements(m[0], m[1], m[2], m[3], m[4], m[5]);
            PrimitiveInverseTransform->Invert();

            // Calculate the new world-space elbow location:

            OverlayOffset.X = OverlayPoints[1].X;
            OverlayOffset.Y = OverlayPoints[1].Y;
            PrimitiveInverseTransform->TransformPoints(&OverlayOffset);

            // Now calculate the new device-space end-points, by initializing
            // in world-space and then converting back to device-space:

            OverlayPoints[0].X = OverlayOffset.X + OverlayDimension;
            OverlayPoints[0].Y = OverlayOffset.Y;

            OverlayPoints[2].X = OverlayOffset.X;
            OverlayPoints[2].Y = OverlayOffset.Y - OverlayDimension;

            PrimitiveTransform->TransformPoints(&OverlayPoints[0]);
            PrimitiveTransform->TransformPoints(&OverlayPoints[2]);

            // We're done; force a redraw:

            EndDialog(hDlg, wParam);
            InvalidateRect(ghwndMain, NULL, TRUE);
        }
        break;

    case WM_SETFOCUS:
        SetFocus(GetDlgItem(hDlg, IDD_OK));
        return(FALSE);

    default:
        return(FALSE);
    }

    return(TRUE);
}

/***************************************************************************\
* ComputeOverlayTransformFromPoints
*
\***************************************************************************/

VOID
ComputeOverlayTransformFromPoints()
{
    REAL dx1 = OverlayPoints[0].X - OverlayPoints[1].X;
    REAL dy1 = OverlayPoints[0].Y - OverlayPoints[1].Y;

    REAL dx2 = OverlayPoints[1].X - OverlayPoints[2].X;
    REAL dy2 = OverlayPoints[1].Y - OverlayPoints[2].Y;

    REAL xMid = (REAL) (WindowWidth >> 1);
    REAL yMid = (REAL) (WindowHeight >> 1);

    RectF srcRect(OverlayOffset.X, OverlayOffset.Y, OverlayDimension, OverlayDimension);

    // The order is upper-left, upper-right, lower-left corner:

    PointF dstPoints[] = { PointF(xMid, yMid),
                           PointF(xMid + dx1, yMid + dy1),
                           PointF(xMid + dx2, yMid + dy2) };

    delete PrimitiveTransform;
    PrimitiveTransform = new Matrix(srcRect, dstPoints);

    delete PrimitiveInverseTransform;
    PrimitiveInverseTransform = PrimitiveTransform->Clone();
    PrimitiveInverseTransform->Invert();
}

/***************************************************************************\
* CreateOverlayTransform
*
\***************************************************************************/

VOID
CreateOverlayTransform()
{
    REAL xMid = (REAL) (WindowWidth >> 1);
    REAL yMid = (REAL) (WindowHeight >> 1);

    OverlayPoints[0].X = xMid + OverlayDimension;
    OverlayPoints[0].Y = yMid;
    OverlayPoints[1].X = xMid;
    OverlayPoints[1].Y = yMid;
    OverlayPoints[2].X = xMid;
    OverlayPoints[2].Y = yMid - OverlayDimension;

    OverlayOffset.X = xMid;
    OverlayOffset.Y = yMid;

    ComputeOverlayTransformFromPoints();
}

/***************************************************************************\
* UpdateOverlay
*
\***************************************************************************/

VOID
UpdateOverlay(
    REAL x,
    REAL y
    )
{
    if (OverlayDragVertex == 1)
    {
        // The root of the overlay is being moved, so we move the overlay
        // as a whole:

        REAL dx = x - OverlayPoints[1].X;
        REAL dy = y - OverlayPoints[1].Y;

        OverlayPoints[0].X += dx;
        OverlayPoints[0].Y += dy;
        OverlayPoints[1].X = x;
        OverlayPoints[1].Y = y;
        OverlayPoints[2].X += dx;
        OverlayPoints[2].Y += dy;
    }
    else
    {
        OverlayPoints[OverlayDragVertex].X = x;
        OverlayPoints[OverlayDragVertex].Y = y;

        ComputeOverlayTransformFromPoints();
    }
}

/***************************************************************************\
* RecenterOverlay
*
\***************************************************************************/

VOID
RecenterOverlay()
{
    REAL xMid = (REAL) (WindowWidth >> 1);
    REAL yMid = (REAL) (WindowHeight >> 1);

    REAL dx = xMid - OverlayPoints[1].X;
    REAL dy = yMid - OverlayPoints[1].Y;

    // Center the transform around the new world-space focus point:

    OverlayOffset.X = OverlayPoints[1].X;
    OverlayOffset.Y = OverlayPoints[1].Y;

    if (PrimitiveInverseTransform != NULL)
    {
        PrimitiveInverseTransform->TransformPoints(&OverlayOffset, 1);
    }

    // Bring the overlay control back to the middle of the screen:

    OverlayPoints[0].X += dx;
    OverlayPoints[0].Y += dy;
    OverlayPoints[1].X = xMid;
    OverlayPoints[1].Y = yMid;
    OverlayPoints[2].X += dx;
    OverlayPoints[2].Y += dy;

    ComputeOverlayTransformFromPoints();
}

/***************************************************************************\
* FindNearest
*
\***************************************************************************/

INT
FindNearest(
    REAL x,                         // Device space
    REAL y,
    const PointF *points,           // World space
    INT count,
    const Matrix *matrix = NULL     // World-to-device transform
    )
{
    INT i;
    REAL d;
    REAL minDistance;
    INT vertex;

    PointF inputPoint(x, y);

    // Find the nearest vertex, using a simple Manhattan metric.

    minDistance = 100000;

    for (i = 0; i < count; i++)
    {
        PointF point(points[i]);

        // For the distance metric, we want to be doing our calculations
        // in device space:

        if (matrix)
        {
            matrix->TransformPoints(&point, 1);
        }

        d = ABS(x - point.X) + ABS(y - point.Y);

        if (d < minDistance)
        {
            minDistance = d;
            vertex = i;
        }
    }

    return((minDistance < HIT_DISTANCE) ? vertex : -1);
}

/***************************************************************************\
* MainWindowProc(hwnd, message, wParam, lParam)
*
* Processes all messages for the main window.
*
* History:
*  04-07-91 -by- KentD
* Wrote it.
\***************************************************************************/

LRESULT
MainWindowProc(
    HWND    hwnd,
    UINT    message,
    WPARAM  wParam,
    LPARAM  lParam
    )
{
    PointF point;
    LONG i;
    LONG d;
    LONG minDistance;
    LONG vertex;
    HDC hdc;
    PAINTSTRUCT ps;

    HMENU hmenu = GetMenu(hwnd);
    WORD mmCommand = LOWORD(wParam);

    switch (message)
    {
    case WM_CREATE:
        // NOTICE-DavePr@2002/05/28
        //   Missing FreeProcInstance for these anyway.

        //glpfnEnterWidth = (FARPROC) MakeProcInstance(EnterWidth, ghwndMain);
        //glpfnEnterAlpha = (FARPROC) MakeProcInstance(EnterAlpha, ghwndMain);
        //glpfnEnterPoints = (FARPROC) MakeProcInstance(EnterPoints, ghwndMain);
        //glpfnEnterTransform = (FARPROC) MakeProcInstance(EnterTransform, ghwndMain);

        SetTimer(hwnd, 1, 80, NULL);
        break;

    case WM_COMMAND:

        switch(mmCommand)
        {
        case MM_RANDOMTEST:
            DoRandomTest = !DoRandomTest;
            if (!DoRandomTest)
                InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_WIDTH:
            //DialogBox(ghInstance, "Width", ghwndMain, glpfnEnterWidth);
            DialogBox(ghInstance, "Width", ghwndMain, EnterWidth);
            CreatePens();
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_ALPHA:
            //DialogBox(ghInstance, "Alpha", ghwndMain, glpfnEnterAlpha);
            DialogBox(ghInstance, "Alpha", ghwndMain, EnterAlpha);
            CreatePens();
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_POINTS:
            //DialogBox(ghInstance, "Points", ghwndMain, glpfnEnterPoints);
            DialogBox(ghInstance, "Points", ghwndMain, EnterPoints);
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_EDITTRANSFORM:
            //DialogBox(ghInstance, "Transform", ghwndMain, glpfnEnterTransform);
            DialogBox(ghInstance, "Transform", ghwndMain, EnterTransform);
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_REDRAW:
            hdc = GetDC(hwnd);
            Draw(hdc);
            ReleaseDC(hwnd, hdc);
            break;

        case MM_TIME:
            hdc = GetDC(hwnd);
            Draw(hdc, TRUE);
            ReleaseDC(hwnd, hdc);
            break;

        case MM_CLIPGRID:
            DoClipGrid = !DoClipGrid;
            CheckMenuItem(hmenu, mmCommand, DoClipGrid ? MF_CHECKED : MF_UNCHECKED);
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_ANTIALIAS:
            DoAntialias = !DoAntialias;
            CheckMenuItem(hmenu, mmCommand, DoAntialias ? MF_CHECKED : MF_UNCHECKED);
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_GAMMACORRECT:
            DoGammaCorrect = !DoGammaCorrect;
            CheckMenuItem(hmenu, mmCommand, DoGammaCorrect ? MF_CHECKED : MF_UNCHECKED);
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_WINDING:
            DoWindingFill = !DoWindingFill;
            CheckMenuItem(hmenu, mmCommand, DoWindingFill ? MF_CHECKED : MF_UNCHECKED);
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_SPINE:
            DoSpine = !DoSpine;
            CheckMenuItem(hmenu, mmCommand, DoSpine ? MF_CHECKED : MF_UNCHECKED);
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_WIDENPATH:
            DoWiden = !DoWiden;
            CheckMenuItem(hmenu, mmCommand, DoWiden ? MF_CHECKED : MF_UNCHECKED);
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_ANCHORS:
            DoAnchors = !DoAnchors;
            CheckMenuItem(hmenu, mmCommand, DoAnchors ? MF_CHECKED : MF_UNCHECKED);
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_TRANSFORMOVERLAY:
            DoTransformOverlay = !DoTransformOverlay;
            CheckMenuItem(hmenu, mmCommand, DoTransformOverlay ? MF_CHECKED : MF_UNCHECKED);
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_SCALINGONLY:
            DoScalingOnly = !DoScalingOnly;
            CheckMenuItem(hmenu, mmCommand, DoScalingOnly ? MF_CHECKED : MF_UNCHECKED);
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_RESETTRANSFORM:
            CreateOverlayTransform();
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_FILL:
            DoFill = !DoFill;
            CheckMenuItem(hmenu, mmCommand, DoFill ? MF_CHECKED : MF_UNCHECKED);
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_DRAW:
            DoDraw = !DoDraw;
            CheckMenuItem(hmenu, mmCommand, DoDraw ? MF_CHECKED : MF_UNCHECKED);
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_POLYGON:
        case MM_LINES:
        case MM_BEZIER:
        case MM_RECTANGLE:
        case MM_ELLIPSE:
        case MM_TEXTPATH:
            CheckMenuItem(hmenu, MmPrimitive, MF_UNCHECKED);
            MmPrimitive = mmCommand;
            CheckMenuItem(hmenu, MmPrimitive, MF_CHECKED);

            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_COMPOUND:
            DoCompound = !DoCompound;
            CheckMenuItem(hmenu, mmCommand, DoCompound ? MF_CHECKED : MF_UNCHECKED);
            CreatePens();
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_STYLE_SOLID:
        case MM_STYLE_DASH:
        case MM_STYLE_DOT:
        case MM_STYLE_DASHDOT:
        case MM_STYLE_DASHDOTDOT:
            CheckMenuItem(hmenu, MmDashStyle, MF_UNCHECKED);
            MmDashStyle = mmCommand;
            CheckMenuItem(hmenu, MmDashStyle, MF_CHECKED);

            CreatePens();
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_CAP_FLAT:
        case MM_CAP_SQUARE:
        case MM_CAP_ROUND:
        case MM_CAP_TRIANGLE:
            CheckMenuItem(hmenu, MmEndCap, MF_UNCHECKED);
            MmEndCap = mmCommand;
            CheckMenuItem(hmenu, MmEndCap, MF_CHECKED);

            CreatePens();
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_JOIN_ROUND:
        case MM_JOIN_MITER:
        case MM_JOIN_BEVEL:
            CheckMenuItem(hmenu, MmJoin, MF_UNCHECKED);
            MmJoin = mmCommand;
            CheckMenuItem(hmenu, MmJoin, MF_CHECKED);

            CreatePens();
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_ALIGNMENT_CENTER: 
        case MM_ALIGNMENT_INSET: 
            CheckMenuItem(hmenu, MmAlignment, MF_UNCHECKED);
            MmAlignment = mmCommand;
            CheckMenuItem(hmenu, MmAlignment, MF_CHECKED);

            CreatePens();
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_WRAP_TILE:
        case MM_WRAP_CLAMP:
        case MM_WRAP_FLIPX:
        case MM_WRAP_FLIPY:
        case MM_WRAP_FLIPXY:
            CheckMenuItem(hmenu, MmWrapMode, MF_UNCHECKED);
            MmWrapMode = mmCommand;
            CheckMenuItem(hmenu, MmWrapMode, MF_CHECKED);

            CreateBrushes();
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_BRUSH_SOLID:
        case MM_BRUSH_TEXTURE:
        case MM_BRUSH_TEXTURE_32x32:
        case MM_BRUSH_TEXTURE_1x1:
        case MM_BRUSH_LINEAR:
        case MM_BRUSH_PATHGRADIENT:
            CheckMenuItem(hmenu, MmBrushType, MF_UNCHECKED);
            MmBrushType = mmCommand;
            CheckMenuItem(hmenu, MmBrushType, MF_CHECKED);

            CreateBrushes();
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_DYNAMICBRUSHRECTANGLE:
            DoBrushRect = !DoBrushRect;
            CheckMenuItem(hmenu, mmCommand, DoBrushRect ? MF_CHECKED : MF_UNCHECKED);
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_EDITBRUSHSHAPE:
            DoShape = !DoShape;
            CheckMenuItem(hmenu, mmCommand, DoShape ? MF_CHECKED : MF_UNCHECKED);
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case MM_GDI:
            DoGdi = !DoGdi;
            CheckMenuItem(hmenu, mmCommand, DoGdi ? MF_CHECKED : MF_UNCHECKED);
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        default:
            break;
        }
        break;

    case WM_SIZE:
        WindowWidth = (short)LOWORD(lParam);
        WindowHeight = (short)HIWORD(lParam);

        CreateRegion_Gdiplus();
        CreateRegion_Gdi();
        RecenterOverlay();

        InvalidateRect(hwnd, NULL, TRUE);
        break;

    case WM_LBUTTONDOWN:
        point.X = (REAL)(short)LOWORD(lParam);
        point.Y = (REAL)(short)HIWORD(lParam);

        // First, try to find a hit with the overlay.  Then try a hit with
        // the primitive points, in world space:

        OverlayDragVertex = -1;
        PrimitiveDragVertex = -1;

        if (DoTransformOverlay)
        {
            OverlayDragVertex = FindNearest(point.X, point.Y, OverlayPoints, 3);
        }

        if (OverlayDragVertex == -1)
        {
            PrimitiveDragVertex = FindNearest(point.X, point.Y, PrimitivePoints, 
                                  PrimitivePointsCount, PrimitiveTransform);
        }

        // The first left-click disables 'adding points' mode:

        IsAddingPoints = FALSE;

        break;

    case WM_RBUTTONDOWN:
        point.X = (REAL)(short)LOWORD(lParam);
        point.Y = (REAL)(short)HIWORD(lParam);
        PrimitiveInverseTransform->TransformPoints(&point, 1);

        // If we were in 'adding points' mode (which occurs when we're
        // right-clicking in succession), simply add the point to the
        // list.  
        //
        // If we're not in 'adding points' mode, reset the point list 
        // and switch us to 'adding points' mode:

        if (!IsAddingPoints)
        {
            IsAddingPoints = TRUE;
            PrimitivePointsCount = 0;
        }

        // Add this point to the list:

        if (PrimitivePointsCount < PrimitivePointsMax)
        {
            PrimitivePoints[PrimitivePointsCount] = point;
            PrimitivePointsCount++;

            // If this was the first point, make all the points the same
            // (in part to make 'ellipse' and 'rectangle' properly empty):

            if (PrimitivePointsCount == 1)
            {
                for (i = 1; i < PrimitivePointsMax; i++)
                {
                    PrimitivePoints[i] = PrimitivePoints[0];
                }
            }

            hdc = GetDC(hwnd);
            Draw(hdc);
            ReleaseDC(hwnd, hdc);
        }

        break;

    case WM_MOUSEMOVE:
        point.X = (REAL)(short)LOWORD(lParam);
        point.Y = (REAL)(short)HIWORD(lParam);

        // Overlay hit-testing works in screen space:
        
        if (OverlayDragVertex != -1)
        {
            // To prevent extraneous redraws, redraw only if the new point
            // is different:

            if ((OverlayPoints[OverlayDragVertex].X != point.X) ||
                (OverlayPoints[OverlayDragVertex].Y != point.Y))
            {
                UpdateOverlay(point.X, point.Y);
    
                hdc = GetDC(hwnd);
                Draw(hdc);
                ReleaseDC(hwnd, hdc);
            }
        }

        // Primitive hit-testing works in world space:

        PrimitiveInverseTransform->TransformPoints(&point, 1);

        if (PrimitiveDragVertex != -1)
        {
            // To prevent extraneous redraws, redraw only if the new point
            // is different:

            if ((PrimitivePoints[PrimitiveDragVertex].X != point.X) ||
                (PrimitivePoints[PrimitiveDragVertex].Y != point.Y))
            {
                PrimitivePoints[PrimitiveDragVertex] = point;
    
                hdc = GetDC(hwnd);
                Draw(hdc);
                ReleaseDC(hwnd, hdc);
            }
        }
        
        break;

    case WM_LBUTTONUP:
        PrimitiveDragVertex = -1;
        OverlayDragVertex = -1;

        RecenterOverlay();
        InvalidateRect(hwnd, NULL, TRUE);
        break;

    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        Draw(hdc);
        ReleaseDC(hwnd, hdc);

        break;

    case WM_TIMER:
        if (DoRandomTest)
        {
            hdc = GetDC(hwnd);
            Draw(hdc);
            ReleaseDC(hwnd, hdc);
        }

        break;

    case WM_DESTROY:
        DeleteObjects_Gdiplus();
        DeleteObjects_Gdi();

        DeleteObject(ghbrWhite);
        PostQuitMessage(0);
        return(DefWindowProc(hwnd, message, wParam, lParam));

    default:
        return(DefWindowProc(hwnd, message, wParam, lParam));
    }

    return(0);
}

/***************************************************************************\
* InitializeApplication()
*
* Initializes app.
*
* History:
*  04-07-91 -by- KentD
* Wrote it.
\***************************************************************************/

BOOL InitializeApplication(VOID)
{
    WNDCLASS wc;

    ghbrWhite = (HBRUSH) GetStockObject(WHITE_BRUSH);

    wc.style            = 0;
    wc.lpfnWndProc      = MainWindowProc;
    wc.cbClsExtra       = 0;
    wc.cbWndExtra       = 0;
    wc.hInstance        = ghInstance;
    wc.hIcon            = LoadIcon(ghInstance, MAKEINTRESOURCE(POLYTESTICON));
    wc.hCursor          = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground    = ghbrWhite;
    wc.lpszMenuName     = "MainMenu";
    wc.lpszClassName    = "TestClass";
    if (!RegisterClass(&wc))
    {
        return(FALSE);
    }
    ghwndMain =
      CreateWindowEx(
        0,
        "TestClass",
        "PolyTest",
        (                      
            WS_OVERLAPPED   |  
            WS_CAPTION      |  
            WS_BORDER       |  
            WS_THICKFRAME   |  
            WS_MAXIMIZEBOX  |  
            WS_MINIMIZEBOX  |  
            WS_CLIPCHILDREN |  
            WS_VISIBLE      |  
            WS_SYSMENU         
        ),
        80,
        70,
        500,
        500,
        NULL,
        NULL,
        ghInstance,
        NULL
        );
    if (ghwndMain == NULL)
    {
        return(FALSE);
    }
    SetFocus(ghwndMain);

    // Create our initialize stuff:

    CreateBrushes();
    CreatePens();
    CreateOverlayTransform();

    return(TRUE);
}

/***************************************************************************\
* main(argc, argv[])
*
* Sets up the message loop.
*
* History:
*  04-07-91 -by- KentD
* Wrote it.
\***************************************************************************/

_cdecl
main(
    INT   argc,
    PCHAR argv[])
{
    MSG    msg;
    HACCEL haccel;
    CHAR*  pSrc;
    CHAR*  pDst;

    if (!gGdiplusInitHelper.IsValid())
    {
        return 0;
    }

    ghInstance = GetModuleHandle(NULL);

    if (!InitializeApplication())
    {
        return(0);
    }

    haccel = LoadAccelerators(ghInstance, MAKEINTRESOURCE(ACCELS));
    while (GetMessage(&msg, NULL, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, haccel, &msg))
        {
             TranslateMessage(&msg);
             DispatchMessage(&msg);
        }
    }
    return(1);
}
