/** \file mswdraw.c
 * Draw basic geometric shapes
 */

/*  XTrackCAD - Model Railroad CAD
 *  Copyright (C) 2005 Dave Bullis
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#define _WIN32_WINNT 0x0600		/* for wheel mouse supposrt */
#include <windows.h>
#include <string.h>
#include <stdlib.h>
#include <commdlg.h>
#include <math.h>
#include <winuser.h>

#define wFont_t tagLOGFONTA

#include "mswint.h"
#include <FreeImage.h>
#include <wingdi.h>

wBool_t wDrawDoTempDraw = TRUE;
/*
 *****************************************************************************
 *
 * Draw
 *
 *****************************************************************************
 */

#define M_PI 3.14159265358979323846

static wBool_t initted = FALSE;

static FARPROC oldDrawProc;

static long setOp = SRCCOPY;
static long clrOp = MERGEPAINT;

#define CENTERMARK_LENGTH 4

static bool bDrawMainBM = 0;

typedef struct {
    double x, y;
} coOrd;

#ifdef SLOW
static wDrawPix_t XWINPIX2DRAWPIX( wDraw_p d, wWinPix_t ix )
{
    return (wDrawPix_t)ix;
}

static wDrawPix_t YWINPIX2DRAWPIX( wDraw_p d, wWinPix_t iy )
{
    wWinPix_t y;
    y = (wDrawPix_t)(d->h-2-iy);
    return y;
}

static wWinPix_t XDRAWPIX2WINPIX( wDraw_p d, wDrawPix_t xx )
{
    wWinPix_t ix;
    ix = (wWinPix_t)(xx);
    return ix;
}

static wWinPix_t YDRAWPIX2WINPIX( wDraw_p d, wDrawPix_t y )
{
    wWinPix_t iy;
    iy = (d->h)-2 - (wWinPix_t)(y);
    return iy;
}

#else
#define XWINPIX2DRAWPIX( d, ix ) \
	((wDrawPix_t)ix)

#define YWINPIX2DRAWPIX( d, iy ) \
	((wDrawPix_t)(d->h-2-iy))

#define XDRAWPIX2WINPIX( d, xx ) \
	((wWinPix_t)(xx))

#define YDRAWPIX2WINPIX( d, y ) \
	(d->h - 2 - (wWinPix_t)(y))
#endif

/*
 *****************************************************************************
 *
 * Basic Line Draw
 *
 *****************************************************************************
 */



void wDrawDelayUpdate(
    wDraw_p d,
    wBool_t delay )
{
}

wBool_t wDrawSetTempMode(
    wDraw_p bd,
    wBool_t bTemp )
{
    wBool_t rc = bd->bTempMode;
    bd->bTempMode = bTemp;
    if (rc == FALSE && bTemp == TRUE) {
        // Main to Temp drawing
        // Copy mainBM to tempBM
        wDrawClearTemp( bd );
        if (bDrawMainBM) {
            return rc;
        }
        HDC hDcOld = CreateCompatibleDC(bd->hDc);
        HBITMAP hBmOld = SelectObject(hDcOld, bd->hBmMain);
        SelectObject(bd->hDc, bd->hBmTemp);
        BitBlt(bd->hDc, 0, 0,
               bd->w, bd->h,
               hDcOld, 0, 0,
               SRCCOPY);
        SelectObject(hDcOld, hBmOld);
        DeleteDC(hDcOld);
        bd->bCopiedMain = TRUE;
    }
    return rc;
}

/**
 * Sets the proper pen and composition for the next drawing operation
 *
 *
 * \param d IN drawing context
 * \param dw IN line width
 * \param lt IN line type (dashed, solid, ...)
 * \param dc IN color
 * \param dopt IN drawing options
 */
static void setDrawMode(
    wDraw_p d,
    wDrawWidth dw,
    wDrawLineType_e lt,
    wDrawColor dc,
    wDrawOpts dopt )
{
    long centerPen[] = {40,10,20,10};
    long phantomPen[] = {40,10,20,10,20,10};

    HPEN hOldPen;
    static wDraw_p d0;
    static wDrawWidth dw0 = -1;
    static wDrawLineType_e lt0 = (wDrawLineType_e)-1;
    static wDrawColor dc0 = -1;
    static LOGBRUSH logBrush = { 0, 0, 0 };
    DWORD penStyle;

    if ( wDrawDoTempDraw && (dopt & wDrawOptTemp) ) {
        SelectObject(d->hDc, d->hBmTemp);
    } else {
        SelectObject(d->hDc, d->hBmMain);
    }

    if ( d->hasPalette ) {
        int winPaletteClock = mswGetPaletteClock();
        if ( d->paletteClock < winPaletteClock ) {
            RealizePalette( d->hDc );
            d->paletteClock = winPaletteClock;
        }
    }

    SetROP2( d->hDc, R2_COPYPEN );
    if ( d == d0 && dw0 == dw && lt == lt0 && dc == dc0 ) {
        return;
    }

    // make sure that the line width is at least 1!
    if( !dw ) {
        dw++;
    }

    d0 = d;
    dw0 = dw;
    lt0 = lt;
    dc0 = dc;

    void * penarray = NULL;
    int penarray_size = 0;

    logBrush.lbColor = mswGetColor(d->hasPalette,dc);
    if ( lt==wDrawLineSolid ) {
        penStyle = PS_GEOMETRIC | PS_SOLID | PS_ENDCAP_FLAT;
    } else if (lt == wDrawLineDot) {
        penStyle = PS_GEOMETRIC | PS_DOT;
    } else if (lt == wDrawLineDash) {
        penStyle = PS_GEOMETRIC | PS_DASH;
    } else if (lt == wDrawLineDashDot) {
        penStyle = PS_GEOMETRIC | PS_DASHDOT;
    } else if ( lt == wDrawLineDashDotDot) {
        penStyle = PS_GEOMETRIC | PS_DASHDOTDOT;
    } else if (  lt == wDrawLineCenter) {
        penStyle = PS_GEOMETRIC | PS_USERSTYLE;
        penarray = &centerPen;
        penarray_size = sizeof(centerPen)/sizeof(long);
    } else if (  lt == wDrawLinePhantom) {
        penStyle = PS_GEOMETRIC | PS_USERSTYLE;
        penarray = &phantomPen;
        penarray_size = sizeof(phantomPen) / sizeof(long);
    } else {
        penStyle = PS_GEOMETRIC | PS_SOLID;
    }
    d->hPen = ExtCreatePen( penStyle,
                            dw,
                            &logBrush,
                            penarray_size,
                            penarray );
    hOldPen = SelectObject( d->hDc, d->hPen );
    DeleteObject( hOldPen );
}

static void setDrawBrush(
    wDraw_p d,
    wDrawColor dc,
    wDrawOpts dopt )
{
    HBRUSH hOldBrush;
    static wDraw_p d0;
    static wDrawColor dc0 = -1;

    setDrawMode( d, 0, wDrawLineSolid, dc, dopt );
    if ( d == d0 && dc == dc0 ) {
        return;
    }

    d0 = d;
    dc0 = dc;

    d->hBrush = CreateSolidBrush(
                    mswGetColor(d->hasPalette,dc) );
    hOldBrush = SelectObject( d->hDc, d->hBrush );
    DeleteObject( hOldBrush );
}


static void myInvalidateRect(
    wDraw_p d,
    RECT * prect )
{
    if ( prect->top < 0 ) {
        prect->top = 0;
    }
    if ( prect->left < 0 ) {
        prect->left = 0;
    }
    if ( prect->bottom > d->h ) {
        prect->bottom = d->h;
    }
    if ( prect->right > d->w ) {
        prect->right = d->w;
    }
    InvalidateRect( d->hWnd, prect, FALSE );
}


static int clip0( POINT * p0, POINT * p1, wDraw_p d )
{
    long int x0=p0->x, y0=p0->y, x1=p1->x, y1=p1->y;
    long int dx, dy;
    if ( x0<0 && x1<0 ) {
        return 0;
    }
    if ( y0<0 && y1<0 ) {
        return 0;
    }
    dx=x1-x0;
    dy=y1-y0;
    if ( x0 < 0 ) {
        y0 -= x0*dy/dx;
        x0 = 0;
    }
    if ( y0 < 0 ) {
        if ( (x0 -= y0*dx/dy) < 0 ) {
            return 0;
        }
        y0 = 0;
    }
    if ( x1 < 0 ) {
        y1 -= x1*dy/dx;
        x1 = 0;
    }
    if ( y1 < 0 ) {
        if ( (x1 -= y1*dx/dy) < 0 ) {
            return 0;
        }
        y1 = 0;
    }
    p0->x = (int)x0;
    p0->y = (int)y0;
    p1->x = (int)x1;
    p1->y = (int)y1;
    return 1;
}


void wDrawLine(
    wDraw_p d,
    wDrawPix_t p0x,
    wDrawPix_t p0y,
    wDrawPix_t p1x,
    wDrawPix_t p1y,
    wDrawWidth dw,
    wDrawLineType_e lt,
    wDrawColor dc,
    wDrawOpts dopt )
{
    POINT p0, p1;
    RECT rect;
    setDrawMode( d, dw, lt, dc, dopt );
    p0.x = XDRAWPIX2WINPIX(d,p0x);
    p0.y = YDRAWPIX2WINPIX(d,p0y);
    p1.x = XDRAWPIX2WINPIX(d,p1x);
    p1.y = YDRAWPIX2WINPIX(d,p1y);

    MoveTo( d->hDc, p0.x, p0.y );
    LineTo( d->hDc, p1.x, p1.y );
    if (d->hWnd) {
        if (dw==0) {
            dw = 1;
        }
        dw++;
        if (p0.y<p1.y) {
            rect.top = p0.y-dw;
            rect.bottom = p1.y+dw;
        } else {
            rect.top = p1.y-dw;
            rect.bottom = p0.y+dw;
        }
        if (p0.x<p1.x) {
            rect.left = p0.x-dw;
            rect.right = p1.x+dw;
        } else {
            rect.left = p1.x-dw;
            rect.right = p0.x+dw;
        }
        myInvalidateRect( d, &rect );
    }
}

static double d2r(double angle)
{
    angle *= (M_PI / 180.0);
    return angle;
}

static double mswsin( double angle )
{
    while (angle < 0.0) {
        angle += 360.0;
    }
    while (angle >= 360.0) {
        angle -= 360.0;
    }
    angle *= (M_PI*2.0)/360.0;
    return sin( angle );
}

static double mswcos( double angle )
{
    while (angle < 0.0) {
        angle += 360.0;
    }
    while (angle >= 360.0) {
        angle -= 360.0;
    }
    angle *= (M_PI*2.0)/360.0;
    return cos( angle );
}

static double mswasin( double x, double h )
{
    double angle;
    angle = asin( x/h );
    angle /= (M_PI*2.0)/360.0;
    return angle;
}

static double mswNormalizeAngle( double a )
{
    while (a<0.0) {
        a += 360.0;
    }
    while (a>=360.0) {
        a -= 360.0;
    }
    return a;
}

/**
 * Draw an arc around a specified center
 *
 * \param d IN ?
 * \param px, py IN  center of arc
 * \param r IN radius
 * \param a0, a1 IN start and end angle
 * \param drawCenter draw marking for center
 * \param dw line width
 * \param lt line type
 * \param dc color
 * \param dopt ?
 */


void wDrawArc(
    wDraw_p d,
    wDrawPix_t px,
    wDrawPix_t py,
    wDrawPix_t r,
    double a0,
    double a1,
    int sizeCenter,
    wDrawWidth dw,
    wDrawLineType_e lt,
    wDrawColor dc,
    wDrawOpts dopt )
{
    int i, cnt;
    POINT p0, p1, ps, pe, pp0, pp1, pp2, pc;
    wDrawPix_t psx, psy, pex, pey;
    double aa, ai;
    RECT rect;
    int needMoveTo;
    wBool_t fakeArc = FALSE;

    // calculate the center coordinates
    pc.x = XDRAWPIX2WINPIX( d, px );
    pc.y = YDRAWPIX2WINPIX( d, py );

    p0.x = XDRAWPIX2WINPIX(d,px-r);
    p0.y = YDRAWPIX2WINPIX(d,py+r);
    p1.x = XDRAWPIX2WINPIX(d,px+r);
    p1.y = YDRAWPIX2WINPIX(d,py-r);

    pex = px + r * mswsin(a0);
    pey = py + r * mswcos(a0);
    psx = px + r * mswsin(a0+a1);
    psy = py + r * mswcos(a0+a1);

    /*pointOnCircle( &pe, p, r, a0 );
    pointOnCircle( &ps, p, r, a0+a1 );*/
    ps.x = XDRAWPIX2WINPIX(d,psx);
    ps.y = YDRAWPIX2WINPIX(d,psy);
    pe.x = XDRAWPIX2WINPIX(d,pex);
    pe.y = YDRAWPIX2WINPIX(d,pey);

    setDrawMode( d, dw, lt, dc, dopt );

    if (dw == 0) {
        dw = 1;
    }

    if ( r > 30000 || a1 < 1.0 ) {
        /* The book says 32K but experience says otherwise */
        fakeArc = TRUE;
    }

    // Starting point
    psx = px + r * mswsin(a0);
    psy = py + r * mswcos(a0);
    pp0.x = XDRAWPIX2WINPIX( d, psx );
    pp0.y = YDRAWPIX2WINPIX( d, psy );

    if ( fakeArc ) {
        cnt = (int)(a1 / 2);
        if ( cnt <= 0 ) {
            cnt = 1;
        }
        if ( cnt > 180 ) {
            cnt = 180;
        }

        ai = d2r(a1) / cnt;
        aa = d2r(a0);
        needMoveTo = TRUE;

        for ( i=0; i<cnt; i++ ) {
            aa += ai;
            psx = px + r * sin(aa);
            psy = py + r * cos(aa);
            pp2.x = pp1.x = XDRAWPIX2WINPIX( d, psx );
            pp2.y = pp1.y = YDRAWPIX2WINPIX( d, psy );
            if ( clip0( &pp0, &pp1, d ) ) {
                if (needMoveTo) {
                    MoveTo( d->hDc, pp0.x, pp0.y );
                    needMoveTo = FALSE;
                }
                LineTo( d->hDc, pp1.x, pp1.y );
            } else {
                needMoveTo = TRUE;
            }
            pp0.x = pp2.x;
            pp0.y = pp2.y;
        }
    } else {
        DWORD rr = XDRAWPIX2WINPIX( d, r );
        SetArcDirection( d->hDc,AD_CLOCKWISE );

        // Draw two arcs from the center to eliminate the odd pie-shaped end artifact
        if ( dw > 2.0 ) {
            double a2 = a1 / 2.0;
            pp2.x = XDRAWPIX2WINPIX( d, px + r * mswsin(a0+a2) );
            pp2.y = YDRAWPIX2WINPIX( d, py + r * mswcos(a0+a2) );

            MoveTo( d->hDc, pp2.x, pp2.y );
            AngleArc( d->hDc, pc.x, pc.y, rr, (float)mswNormalizeAngle(90 - (a0+a2)),
                      (float)(-a2) );
            MoveTo( d->hDc, pp2.x, pp2.y );
            AngleArc( d->hDc, pc.x, pc.y, rr, (float)mswNormalizeAngle(90 - (a0+a2)),
                      (float)(a2) );
        } else {
            MoveTo( d->hDc, pp0.x, pp0.y );
            AngleArc( d->hDc, pc.x, pc.y, rr, (float)mswNormalizeAngle(90 - a0),
                      (float)(-a1) );
        }
    }

    // should the center of the arc be drawn?
    if( sizeCenter ) {

        // now draw the crosshair
        MoveTo( d->hDc, pc.x - CENTERMARK_LENGTH*sizeCenter, pc.y );
        LineTo( d->hDc, pc.x + CENTERMARK_LENGTH*sizeCenter, pc.y );
        MoveTo( d->hDc, pc.x, pc.y - CENTERMARK_LENGTH*sizeCenter );
        LineTo( d->hDc, pc.x, pc.y + CENTERMARK_LENGTH*sizeCenter );

        // invalidate the area of the crosshair
        rect.top  = pc.y - CENTERMARK_LENGTH*sizeCenter - 1;
        rect.bottom  = pc.y + CENTERMARK_LENGTH*sizeCenter + 1;
        rect.left = pc.x - CENTERMARK_LENGTH*sizeCenter - 1;
        rect.right = pc.x + CENTERMARK_LENGTH*sizeCenter + 1;
        myInvalidateRect( d, &rect );
    }

    if (d->hWnd) {
        dw++;
        a1 += a0;
        if (a1>360.0) {
            rect.top = p0.y;
        } else {
            rect.top = min(pe.y,ps.y);
        }
        if (a1>(a0>180?360.0:0.0)+180) {
            rect.bottom = p1.y;
        } else {
            rect.bottom = max(pe.y,ps.y);
        }
        if (a1>(a0>270?360.0:0.0)+270) {
            rect.left = p0.x;
        } else {
            rect.left = min(pe.x,ps.x);
        }
        if (a1>(a0>90?360.0:0.0)+90) {
            rect.right = p1.x;
        } else {
            rect.right = max(pe.x,ps.x);
        }
        rect.top -= dw;
        rect.bottom += dw;
        rect.left -= dw;
        rect.right += dw;
        myInvalidateRect( d, &rect );

    }
}

void wDrawPoint(
    wDraw_p d,
    wDrawPix_t px,
    wDrawPix_t py,
    wDrawColor dc,
    wDrawOpts dopt )
{
    POINT p0;
    RECT rect;

    p0.x = XDRAWPIX2WINPIX(d,px);
    p0.y = YDRAWPIX2WINPIX(d,py);

    if ( p0.x < 0 || p0.y < 0 ) {
        return;
    }
    if ( p0.x >= d->w || p0.y >= d->h ) {
        return;
    }
    setDrawMode( d, 0, wDrawLineSolid, dc, dopt );

    SetPixel( d->hDc, p0.x, p0.y, mswGetColor(d->hasPalette,
              dc) /*colorPalette.palPalEntry[dc]*/ );
    if (d->hWnd) {
        rect.top = p0.y-1;
        rect.bottom = p0.y+1;
        rect.left = p0.x-1;
        rect.right = p0.x+1;
        myInvalidateRect( d, &rect );
    }
}

/*
 *****************************************************************************
 *
 * Fonts
 *
 *****************************************************************************
 */


static LOGFONT logFont = {
    /* Initial default values */
    -24, 0, /* H, W */
    0,		/* A */
    0,
    FW_REGULAR,
    0, 0, 0,/* I, U, SO */
    ANSI_CHARSET,
    0,		/* OP */
    0,		/* CP */
    0,		/* Q */
    0,		/* P&F */
    "Arial"
};

static LOGFONT timesFont[2][2] = {
    { {
            /* Initial default values */
            0, 0,	/* H, W */
            0,		/* A */
            0,
            FW_REGULAR,
            0, 0, 0,/* I, U, SO */
            ANSI_CHARSET,
            0,		/* OP */
            0,		/* CP */
            0,		/* Q */
            0,		/* P&F */
            "Times"
        },
        {
            /* Initial default values */
            0, 0,	/* H, W */
            0,		/* A */
            0,
            FW_REGULAR,
            1, 0, 0,/* I, U, SO */
            ANSI_CHARSET,
            0,		/* OP */
            0,		/* CP */
            0,		/* Q */
            0,		/* P&F */
            "Times"
        }
    },
    { {
            /* Initial default values */
            0, 0,	/* H, W */
            0,		/* A */
            0,
            FW_BOLD,
            0, 0, 0,/* I, U, SO */
            ANSI_CHARSET,
            0,		/* OP */
            0,		/* CP */
            0,		/* Q */
            0,		/* P&F */
            "Times"
        },
        {
            /* Initial default values */
            0, 0,	/* H, W */
            0,		/* A */
            0,
            FW_BOLD,
            1, 0, 0,/* I, U, SO */
            ANSI_CHARSET,
            0,		/* OP */
            0,		/* CP */
            0,		/* Q */
            0,		/* P&F */
            "Times"
        }
    }
};

static LOGFONT helvFont[2][2] = {
    { {
            /* Initial default values */
            0, 0,	/* H, W */
            0,		/* A */
            0,
            FW_REGULAR,
            0, 0, 0,/* I, U, SO */
            ANSI_CHARSET,
            0,		/* OP */
            0,		/* CP */
            0,		/* Q */
            0,		/* P&F */
            "Arial"
        },
        {
            /* Initial default values */
            0, 0,	/* H, W */
            0,		/* A */
            0,
            FW_REGULAR,
            1, 0, 0,/* I, U, SO */
            ANSI_CHARSET,
            0,		/* OP */
            0,		/* CP */
            0,		/* Q */
            0,		/* P&F */
            "Arial"
        }
    },
    { {
            /* Initial default values */
            0, 0,	/* H, W */
            0,		/* A */
            0,
            FW_BOLD,
            0, 0, 0,/* I, U, SO */
            ANSI_CHARSET,
            0,		/* OP */
            0,		/* CP */
            0,		/* Q */
            0,		/* P&F */
            "Arial"
        },
        {
            /* Initial default values */
            0, 0,	/* H, W */
            0,		/* A */
            0,
            FW_BOLD,
            1, 0, 0,/* I, U, SO */
            ANSI_CHARSET,
            0,		/* OP */
            0,		/* CP */
            0,		/* Q */
            0,		/* P&F */
            "Hevletica"
        }
    }
};


void mswFontInit( void )
{
    const char * face;
    long size;
    /** @prefs [msw window font] face=FontName */
    face = wPrefGetString( "msw window font", "face" );
    /** @prefs [msw window font] size=-24 */
    wPrefGetInteger( "msw window font", "size", &size, -24 );
    if (face) {
        strncpy( logFont.lfFaceName, face, LF_FACESIZE );
    }
    logFont.lfHeight = (int)size;
}


static CHOOSEFONT chooseFont;
static wFontSize_t fontSize = 18;
static double fontFactor = 1.0;

static void doChooseFont( void )
{
    int rc;
    memset( &chooseFont, 0, sizeof chooseFont );
    chooseFont.lStructSize = sizeof chooseFont;
    chooseFont.hwndOwner = mswHWnd;
    chooseFont.lpLogFont = &logFont;
    chooseFont.Flags = CF_SCREENFONTS|CF_SCALABLEONLY|CF_INITTOLOGFONTSTRUCT;
    chooseFont.nFontType = SCREEN_FONTTYPE;
    rc = ChooseFont( &chooseFont );
    if (rc) {
        fontSize = (wFontSize_t)(-logFont.lfHeight * 72) / 96.0 / fontFactor;
        if (fontSize < 1) {
            fontSize = 1;
        }
        wPrefSetString( "msw window font", "face", logFont.lfFaceName );
        wPrefSetInteger( "msw window font", "size", logFont.lfHeight );
    }
}

static int computeFontSize( wDraw_p d, double siz )
{
    int ret;
    siz = (siz * d->DPI) / 72.0;
    ret = (int)(siz * fontFactor);
    if (ret < 1) {
        ret = 1;
    }
    return -ret;
}

void wDrawGetTextSize(
    wDrawPix_t *w,
    wDrawPix_t *h,
    wDrawPix_t *d,
    wDrawPix_t *a,
    wDraw_p bd,
    const char * text,
    wFont_p fp,
    double siz )
{
    wWinPix_t x, y;
    HFONT newFont, prevFont;
    DWORD extent;
    int oldLfHeight;
    TEXTMETRIC textMetric;

    if (fp == NULL) {
        fp = &logFont;
    }
    fp->lfEscapement = 0;
    oldLfHeight = fp->lfHeight;
    fp->lfHeight = computeFontSize( bd, siz );
    fp->lfWidth = 0;
    newFont = CreateFontIndirect( fp );
    prevFont = SelectObject( bd->hDc, newFont );
    extent = GetTextExtent( bd->hDc, CAST_AWAY_CONST text, (int)(strlen(text)) );

    GetTextMetrics(bd->hDc, &textMetric);

    x = LOWORD(extent);
    y = HIWORD(extent);
    *w = (wDrawPix_t)x;
    *h = (wDrawPix_t)y;
    *d = (wDrawPix_t)textMetric.tmDescent;
    *a = (wDrawPix_t)textMetric.tmAscent;

    SelectObject( bd->hDc, prevFont );
    DeleteObject( newFont );
    fp->lfHeight = oldLfHeight;
}
/**
 * Draw text
 *
 * \param d	device context
 * \param px position x
 * \param py position y
 * \param angle drawing angle
 * \param text text to print
 * \param fp font
 * \param siz font size
 * \param dc color
 * \param dopts drawing options
 */
void wDrawString(
    wDraw_p d,
    wDrawPix_t px,
    wDrawPix_t py,
    double angle,
    const char * text,
    wFont_p fp,
    double siz,
    wDrawColor dc,
    wDrawOpts dopts)
{
    int x, y;
    HFONT newFont, prevFont;
    DWORD extent;
    int w, h;
    RECT rect;
    int oldLfHeight;

    if (fp == NULL) {
        fp = &logFont;
    }

    oldLfHeight = fp->lfHeight;
    fp->lfEscapement = (int)(angle*10.0);
    fp->lfHeight = computeFontSize(d, siz);
    fp->lfWidth = 0;
    newFont = CreateFontIndirect(fp);
    x = XDRAWPIX2WINPIX(d,px) + (int)(mswsin(angle)*fp->lfHeight-0.5);
    y = YDRAWPIX2WINPIX(d,py) + (int)(mswcos(angle)*fp->lfHeight-0.5);

    setDrawMode( d, 0, wDrawLineSolid, dc, dopts );
    prevFont = SelectObject(d->hDc, newFont);
    SetBkMode(d->hDc, TRANSPARENT);

    if (dopts & wDrawOutlineFont) {
        HPEN oldPen;
        BeginPath(d->hDc);
        TextOut(d->hDc, x, y, text, (int)strlen(text));
        EndPath(d->hDc);

        // Now draw outline text
        oldPen = SelectObject(d->hDc,
                              CreatePen(PS_SOLID, 1,
                                        mswGetColor(d->hasPalette, dc)));
        StrokePath(d->hDc);
        SelectObject(d->hDc, oldPen);
    } else {
        COLORREF old;

        old = SetTextColor(d->hDc, mswGetColor(d->hasPalette, dc));
        TextOut(d->hDc, x, y, text, (int)(strlen(text)));
        SetTextColor(d->hDc, old);
    }

    extent = GetTextExtent(d->hDc, CAST_AWAY_CONST text, (int)(strlen(text)));
    SelectObject(d->hDc, prevFont);
    w = LOWORD(extent);
    h = HIWORD(extent);

    if (d->hWnd) {
        rect.top = y - (w + h + 1);
        rect.bottom = y + (w + h + 1);
        rect.left = x - (w + h + 1);
        rect.right = x + (w + h + 1);
        myInvalidateRect(d, &rect);
    }

    DeleteObject(newFont);
    fp->lfHeight = oldLfHeight;
}

static const char * wCurFont( void )
{
    return logFont.lfFaceName;
}

void wInitializeFonts()
{
}

wFont_p wStandardFont( int family, wBool_t bold, wBool_t italic )
{
    if (family == F_TIMES) {
        return &timesFont[bold][italic];
    } else if (family == F_HELV) {
        return &helvFont[bold][italic];
    } else {
        return NULL;
    }
}

void wSelectFont( const char * title )
{
    doChooseFont();
}


wFontSize_t wSelectedFontSize( void )
{
    return fontSize;
}

void wSetSelectedFontSize(wFontSize_t size)
{
    fontSize = size;
}

/*
 *****************************************************************************
 *
 * Misc
 *
 *****************************************************************************
 */



void wDrawFilledRectangle(
    wDraw_p d,
    wDrawPix_t px,
    wDrawPix_t py,
    wDrawPix_t sx,
    wDrawPix_t sy,
    wDrawColor color,
    wDrawOpts opts )
{
    int mode;
    RECT rect;
    if (d == NULL) {
        return;
    }
    setDrawBrush( d, color, opts );
    if (opts & wDrawOptTransparent) {
        mode = R2_NOTXORPEN;
    } else {
        mode = R2_COPYPEN;
    }
    SetROP2(d->hDc, mode);
    rect.left = XDRAWPIX2WINPIX(d,px);
    rect.right = XDRAWPIX2WINPIX(d,px+sx);
    rect.top = YDRAWPIX2WINPIX(d,py+sy);
    rect.bottom = YDRAWPIX2WINPIX(d,py);
    if ( rect.right < 0 ||
            rect.bottom < 0 ) {
        return;
    }
    if ( rect.left < 0 ) {
        rect.left = 0;
    }
    if ( rect.top < 0 ) {
        rect.top = 0;
    }
    if ( rect.left > d->w ||
            rect.top > d->h ) {
        return;
    }
    if ( rect.right > d->w ) {
        rect.right = d->w;
    }
    if ( rect.bottom > d->h ) {
        rect.bottom = d->h;
    }
    Rectangle( d->hDc, rect.left, rect.top, rect.right, rect.bottom );
    if (d->hWnd) {
        rect.top--;
        rect.left--;
        rect.bottom++;
        rect.right++;
        myInvalidateRect( d, &rect );
    }
}

#ifdef DRAWFILLPOLYLOG
static FILE * logF;
#endif

static dynArr_t wFillPoints_da;
static dynArr_t wFillType_da;

#define POINTTYPE(N) DYNARR_N( BYTE, wFillType_da, (N) )
#define POINTPOS(N) DYNARR_N( POINT, wFillPoints_da, (N) )

/**
 * Add a point definition to the list. The clipping rectangle is recalculated to
 * include the new point.
 *
 * \param d IN drawing context
 * \param pk IN index of new point
 * \param pp IN pointer to the point's coordinates
 * \param type IN line type
 * \param pr IN/OUT clipping rectangle
 */

static void addPoint(
    wDraw_p d,
    int pk,
    coOrd * pp,
    BYTE type, RECT * pr)
{
    POINT p;
    p.x = XDRAWPIX2WINPIX(d, pp->x);
    p.y = YDRAWPIX2WINPIX(d, pp->y);

#ifdef DRAWFILLPOLYLOG
    fprintf(logF, "	q[%d] = {%d,%d}\n", pk, p.x, p.y);
#endif

    DYNARR_N(POINT, wFillPoints_da, pk) = p;
    DYNARR_N(BYTE, wFillType_da, pk) = type;

    if (p.x < pr->left) {
        pr->left = p.x;
    }
    if (p.x > pr->right) {
        pr->right = p.x;
    }
    if (p.y < pr->top) {
        pr->top = p.y;
    }
    if (p.y > pr->bottom) {
        pr->bottom = p.y;
    }
}

/**
 * Draw a polyline consisting of straights with smoothed or rounded corners.
 * Optionally the area can be filled.
 *
 * \param d	IN	drawing context
 * \param node IN 2 dimensional array of coordinates
 * \param type IN type of corener (vertex, smooth or round)
 * \param cnt IN number of points
 * \param color IN color
 * \param dw IN line width
 * \param lt IN line type
 * \param opts IN drawing options
 * \param fill IN area will be filled if true
 * \param open IN do not close area
 */

void wDrawPolygon(
    wDraw_p d,
    wDrawPix_t node[][2],
    wPolyLine_e type[],
    wIndex_t cnt,
    wDrawColor color,
    wDrawWidth dw,
    wDrawLineType_e lt,
    wDrawOpts opts,
    int fill,
    int open)
{
    RECT rect;
    int i, prevNode, nextNode;
    int pointCount = 0;
    coOrd endPoint0, endPoint1, controlPoint0, controlPoint1;
    coOrd point, startingPoint;
    BOOL rc;
    int closed = 0;

    if (d == NULL) {
        return;
    }

    // make sure the array for the points is large enough
    // worst case are rounded corners that require 4 points
    DYNARR_RESET(POINT,wFillPoints_da);
    DYNARR_SET(POINT,wFillPoints_da,(cnt + 1) * 4);
    DYNARR_RESET(BYTE,wFillType_da);
    DYNARR_SET(POINT,wFillType_da, (cnt + 1) * 4);

    BeginPath(d->hDc);

    if (fill) {
        int mode;
        setDrawBrush(d, color, opts);
        if (opts & wDrawOptTransparent) {
            mode = R2_NOTXORPEN;
        } else {
            mode = R2_COPYPEN;
        }
        SetROP2(d->hDc, mode);

    } else {
        setDrawMode(d, dw, lt, color, opts);
    }

    rect.left = rect.right = XDRAWPIX2WINPIX(d,node[cnt-1][0]-1);
    rect.top = rect.bottom = YDRAWPIX2WINPIX(d,node[cnt-1][1]+1);

#ifdef DRAWFILLPOLYLOG
    logF = fopen("log.txt", "a");
    fprintf(logF, "\np[%d] = {%d,%d}\n", cnt-1, node[0][0], node[0][1]);
#endif

    for (i=0; i<cnt; i++) {
        wPolyLine_e type1;
        point.x = node[i][0];
        point.y = node[i][1];
        if (type != NULL) {
            type1 = type[i];
        } else {
            type1 = wPolyLineStraight;
        }

        if (type1 == wPolyLineRound || type1 == wPolyLineSmooth) {
            prevNode = (i == 0) ? cnt - 1 : i - 1;
            nextNode = (i == cnt - 1) ? 0 : i + 1;

            // calculate distance to neighboring nodes
            int prevXDistance = (wWinPix_t)(node[i][0] - node[prevNode][0]);
            int prevYDistance = (wWinPix_t)(node[i][1] - node[prevNode][1]);
            int nextXDistance = (wWinPix_t)(node[nextNode][0]-node[i][0]);
            int nextYDistance = (wWinPix_t)(node[nextNode][1]-node[i][1]);

            // distance from node to endpoints of curve is half the line length
            endPoint0.x = (prevXDistance/2)+node[prevNode][0];
            endPoint0.y = (prevYDistance/2)+node[prevNode][1];
            endPoint1.x = (nextXDistance/2)+node[i][0];
            endPoint1.y = (nextYDistance/2)+node[i][1];

            if (type1 == wPolyLineRound) {
                double distNext = (nextXDistance*nextXDistance + nextYDistance * nextYDistance);
                double distPrev = (prevXDistance*prevXDistance + prevYDistance * prevYDistance);
                // but should be half of the shortest line length (equidistant from node) for round
                if ((distPrev > 0) && (distNext > 0)) {
                    double ratio = sqrt(distPrev / distNext);
                    if (distPrev < distNext) {
                        endPoint1.x = ((nextXDistance*ratio) / 2) + node[i][0];
                        endPoint1.y = ((nextYDistance*ratio) / 2) + node[i][1];
                    } else {
                        endPoint0.x = node[i][0] - (prevXDistance / (2 * ratio));
                        endPoint0.y = node[i][1] - (prevYDistance / (2 * ratio));
                    }
                }
                // experience says that the best look is achieved if the
                // control points are in the middle between end point and node
                controlPoint0.x = (node[i][0] - endPoint0.x) / 2 + endPoint0.x;
                controlPoint0.y = (node[i][1] - endPoint0.y) / 2 + endPoint0.y;

                controlPoint1.x = (endPoint1.x - node[i][0]) / 2 + node[i][0];
                controlPoint1.y = (endPoint1.y - node[i][1]) / 2 + node[i][1];
            } else {
                controlPoint0 = point;
                controlPoint1 = point;
            }
        }

        if (i==0) {
            if (type1 == wPolyLineStraight || open) {
                // for straight lines or open shapes use the starting point as passed
                addPoint(d, pointCount++, &point, PT_MOVETO, &rect);
                startingPoint = point;
            } else {
                // for Bezier begin with the calculated starting point
                addPoint(d, pointCount++, &endPoint0, PT_MOVETO, &rect);
                addPoint(d, pointCount++, &controlPoint0, PT_BEZIERTO, &rect);
                addPoint(d, pointCount++, &controlPoint1, PT_BEZIERTO, &rect);
                addPoint(d, pointCount++, &endPoint1, PT_BEZIERTO, &rect);
                startingPoint = endPoint0;
            }
        } else {
            if (type1 == wPolyLineStraight || (open && (i==cnt-1))) {
                addPoint(d, pointCount++, &point, PT_LINETO, &rect);
            } else {
                if (i==cnt-1 && !open) {
                    closed = TRUE;
                }
                addPoint(d, pointCount++, &endPoint0, PT_LINETO, &rect);
                addPoint(d, pointCount++, &controlPoint0, PT_BEZIERTO, &rect);
                addPoint(d, pointCount++, &controlPoint1, PT_BEZIERTO, &rect);
                addPoint(d, pointCount++, &endPoint1,
                         PT_BEZIERTO | (closed ? PT_CLOSEFIGURE : 0), &rect);
            }
        }
    }

    if (!open && !closed) {
        addPoint(d, pointCount++, &startingPoint, PT_LINETO, &rect);
    }
    rc = PolyDraw(d->hDc, wFillPoints_da.ptr, wFillType_da.ptr, pointCount);

    EndPath(d->hDc);

    if (fill && !open) {
        FillPath(d->hDc);
    } else {
        StrokePath(d->hDc);
    }

    if (d->hWnd) {
        rect.top--;
        rect.left--;
        rect.bottom++;
        rect.right++;
        myInvalidateRect(d, &rect);
    }
}

#define MAX_FILLCIRCLE_POINTS	(30)
void wDrawFilledCircle(
    wDraw_p d,
    wDrawPix_t x,
    wDrawPix_t y,
    wDrawPix_t r,
    wDrawColor color,
    wDrawOpts opts )
{
    POINT p0, p1;
    RECT rect;
    static wDrawPix_t circlePts[MAX_FILLCIRCLE_POINTS][2];

    p0.x = XDRAWPIX2WINPIX(d,x-r);
    p0.y = YDRAWPIX2WINPIX(d,y+r);
    p1.x = XDRAWPIX2WINPIX(d,x+r);
    p1.y = YDRAWPIX2WINPIX(d,y-r);

    setDrawBrush( d, color, opts );
    Ellipse( d->hDc, p0.x, p0.y, p1.x, p1.y );
    if (d->hWnd) {
        rect.top = p0.y;
        rect.bottom = p1.y;
        rect.left = p0.x;
        rect.right = p1.x;
        myInvalidateRect( d, &rect );
    }
}

/*
 *****************************************************************************
 *
 * Misc
 *
 *****************************************************************************
 */


void wDrawSaveImage(
    wDraw_p bd )
{
    if ( bd->hBmBackup ) {
        SelectObject( bd->hDcBackup, bd->hBmBackupOld );
        DeleteObject( bd->hBmBackup );
        bd->hBmBackup = (HBITMAP)0;
    }
    if ( bd->hDcBackup == (HDC)0 ) {
        bd->hDcBackup = CreateCompatibleDC( bd->hDc );
    }
    bd->hBmBackup = CreateCompatibleBitmap( bd->hDc, bd->w, bd->h );
    bd->hBmBackupOld = SelectObject( bd->hDcBackup, bd->hBmBackup );
    BitBlt( bd->hDcBackup, 0, 0, bd->w, bd->h, bd->hDc, 0, 0, SRCCOPY );
}

void wDrawRestoreImage(
    wDraw_p bd )
{
    if ( bd->hBmBackup == (HBITMAP)0 ) {
        mswFail( "wDrawRestoreImage: hBmBackup == 0" );
        return;
    }
    BitBlt( bd->hDc, 0, 0, bd->w, bd->h, bd->hDcBackup, 0, 0, SRCCOPY );
    InvalidateRect( bd->hWnd, NULL, FALSE );
}


void wDrawClearTemp( wDraw_p d )
{
    RECT rect;
    SelectObject( d->hDc, d->hBmTemp );
    BitBlt(d->hDc, 0, 0, d->w, d->h, d->hDc, 0, 0, WHITENESS);
    if (d->hWnd) {
        rect.top = 0;
        rect.bottom = d->h;
        rect.left = 0;
        rect.right = d->w;
        InvalidateRect( d->hWnd, &rect, FALSE );
    }
}


void wDrawClear( wDraw_p d )
{
    SelectObject( d->hDc, d->hBmMain );
    // BitBlt is faster than Rectangle
    BitBlt(d->hDc, 0, 0, d->w, d->h, d->hDc, 0, 0, WHITENESS);
    wDrawClearTemp(d);
}


void wDrawSetSize(
    wDraw_p d,
    wWinPix_t width,
    wWinPix_t height, void * redraw)
{
    d->w = width;
    d->h = height;
    if (!SetWindowPos( d->hWnd, HWND_TOP, 0, 0,
                       d->w, d->h, SWP_NOMOVE|SWP_NOZORDER)) {
        mswFail("wDrawSetSize: SetWindowPos");
    }
    /*wRedraw( d );*/
}


void wDrawGetSize(
    wDraw_p d,
    wWinPix_t * width,
    wWinPix_t * height )
{
    *width = d->w-2;
    *height = d->h-2;
}


void * wDrawGetContext( wDraw_p d )
{
    return d->data;
}


double wDrawGetDPI( wDraw_p d )
{
    return d->DPI;
}

double wDrawGetMaxRadius( wDraw_p d )
{
    return 4096.0;
}

void wDrawClip(
    wDraw_p d,
    wDrawPix_t x,
    wDrawPix_t y,
    wDrawPix_t w,
    wDrawPix_t h )
{
    wWinPix_t ix0, iy0, ix1, iy1;
    HRGN hRgnClip;
    ix0 = XDRAWPIX2WINPIX(d,x);
    iy0 = YDRAWPIX2WINPIX(d,y);
    ix1 = XDRAWPIX2WINPIX(d,x+w);
    iy1 = YDRAWPIX2WINPIX(d,y+h);
    /* Note: Ydim is upside down so iy1<iy0 */
    hRgnClip = CreateRectRgn( ix0, iy1, ix1, iy0 );
    SelectClipRgn( d->hDc, hRgnClip );
    DeleteObject( hRgnClip );
}


void wRedraw( wDraw_p d )
{
    wDrawClear( d );
    if (d->drawRepaint) {
        d->drawRepaint( d, d->data, 0, 0 );
    }
}

/*
 *****************************************************************************
 *
 * Cursor handling
 *
 *****************************************************************************
 */


extern long dontHideCursor;
static wCursor_t curCursor = wCursorNormal;

void DoSetCursor()
{
    switch (curCursor) {
    case wCursorNormal:
    default:
        SetCursor(LoadCursor(NULL, IDC_ARROW));
        break;

    case wCursorWait:
        SetCursor(LoadCursor(NULL, IDC_WAIT));
        break;

    case wCursorCross:
        SetCursor(LoadCursor(NULL, IDC_CROSS));
        break;

    case wCursorIBeam:
        SetCursor(LoadCursor(NULL, IDC_IBEAM));
        break;

    case wCursorQuestion:
        SetCursor(LoadCursor(NULL, IDC_HELP));
        break;

    case wCursorHand:
        SetCursor(LoadCursor(NULL, IDC_HAND));
        break;

    case wCursorNo:
        SetCursor(LoadCursor(NULL, IDC_NO));
        break;

    case wCursorSizeAll:
        SetCursor(LoadCursor(NULL, IDC_SIZEALL));
        break;

    case wCursorSizeNESW:
        SetCursor(LoadCursor(NULL, IDC_SIZENESW));
        break;

    case wCursorSizeNWSE:
        SetCursor(LoadCursor(NULL, IDC_SIZENWSE));
        break;

    case wCursorSizeNS:
        SetCursor(LoadCursor(NULL, IDC_SIZENS));
        break;

    case wCursorSizeWE:
        SetCursor(LoadCursor(NULL, IDC_SIZEWE));
        break;

    case wCursorAppStart:
        SetCursor(LoadCursor(NULL, IDC_APPSTARTING));
        break;

    case wCursorNone:
        if (!dontHideCursor) {
            SetCursor(NULL);
        }
        break;
    }

}

void wSetCursor(wDraw_p win,
                wCursor_t cursor)
{
    curCursor = cursor;
    DoSetCursor();
}



/*
 *****************************************************************************
 *
 * BitMap
 *
 *****************************************************************************
 */

struct wDrawBitMap_t {
    wDrawBitMap_p next;
    wDrawPix_t x;
    wDrawPix_t y;
    wDrawPix_t w;
    wDrawPix_t h;
    char * bmx;
    wDrawColor color;
    HBITMAP bm;
};
static wDrawBitMap_p bmRoot = NULL;

extern wDrawColor drawColorWhite;
extern wDrawColor drawColorBlack;

void wDrawBitMap(
    wDraw_p d,
    wDrawBitMap_p bm,
    wDrawPix_t px,
    wDrawPix_t py,
    wDrawColor dc,
    wDrawOpts dopt )
{
    HDC bmDc;
    HBITMAP oldBm;
    DWORD mode;
    int x0, y0;
    RECT rect;

    x0 = XDRAWPIX2WINPIX(d,px-bm->x);
    y0 = YDRAWPIX2WINPIX(d,py-bm->y+bm->h);
#ifdef LATER
    if ( noNegDrawArgs > 0 && ( x0 < 0 || y0 < 0 ) ) {
        return;
    }
#endif
    if (dc == drawColorWhite) {
        mode = clrOp;
        dc = drawColorBlack;
    } else {
        mode = setOp;
    }

    if ( bm->color != dc ) {
        if ( bm->bm ) {
            DeleteObject( bm->bm );
        }
        bm->bm = mswCreateBitMap( mswGetColor(d->hasPalette,
                                              dc) /*colorPalette.palPalEntry[dc]*/, RGB(255, 255, 255),
                                  RGB( 255, 255, 255 ), (wWinPix_t)bm->w, (wWinPix_t)bm->h, bm->bmx );
        bm->color = dc;
    }

    bmDc = CreateCompatibleDC( d->hDc );
    setDrawMode( d, 0, wDrawLineSolid, dc, dopt );
    oldBm = SelectObject( bmDc, bm->bm );
    BitBlt( d->hDc, x0, y0, (wWinPix_t)bm->w, (wWinPix_t)bm->h, bmDc, 0, 0, mode );
    SelectObject( bmDc, oldBm );
    DeleteDC( bmDc );
    if (d->hWnd) {
        rect.top = y0-1;
        rect.bottom = rect.top+ (wWinPix_t)bm->h+1;
        rect.left = x0-1;
        rect.right = rect.left+ (wWinPix_t)bm->w+1;
        myInvalidateRect( d, &rect );
    }
}


wDrawBitMap_p wDrawBitMapCreate(
    wDraw_p d,
    int w,
    int h,
    int x,
    int y,
    const unsigned char * bits )
{
    wDrawBitMap_p bm;
    int bmSize = ((w+7)/8) * h;
    bm = (wDrawBitMap_p)malloc( sizeof *bm );
    if (bmRoot == NULL) {
        bmRoot = bm;
        bm->next = NULL;
    } else {
        bm->next = bmRoot;
        bmRoot = bm;
    }
    bm->x = x;
    bm->y = y;
    bm->w = w;
    bm->h = h;
    bm->bmx = malloc( bmSize );
    bm->bm = (HBITMAP)0;
    bm->color = -1;
    memcpy( bm->bmx, bits, bmSize );
    /*bm->bm = mswCreateBitMap( GetSysColor(COLOR_BTNTEXT), RGB( 255, 255, 255 ), w, h, bits );*/
    return bm;
}

/*
 *****************************************************************************
 *
 * Create
 *
 *****************************************************************************
 */

static int doSetFocus = 1;

LRESULT FAR PASCAL XEXPORT mswDrawPush(
    HWND hWnd,
    UINT message,
    WPARAM wParam,
    LPARAM lParam )
{
    wIndex_t inx = (wIndex_t)GetWindowLongPtr( hWnd, GWL_ID );
    wDraw_p b;
    wWinPix_t ix, iy;
    wDrawPix_t x, y;
    HDC hDc;
    PAINTSTRUCT ps;
    wAction_t action;
    RECT rect;
    HWND activeWnd;
    HWND focusWnd;
    wAccelKey_e extChar;

    switch( message ) {
    case WM_CREATE:
        b = (wDraw_p)mswMapIndex( inx );
        hDc = GetDC(hWnd);
        if ( b->option & BD_DIRECT ) {
            b->hDc = hDc;
            b->hBmMain = 0;
            b->hBmTemp = 0;
            b->hBmOld = 0;
        } else {
            b->hDc = CreateCompatibleDC( hDc );
            b->hBmMain = CreateCompatibleBitmap( hDc, b->w, b->h );
            b->hBmTemp = CreateCompatibleBitmap( hDc, b->w, b->h );
            b->hBmOld = SelectObject( b->hDc, b->hBmMain );
        }
        if (mswPalette) {
            SelectPalette( b->hDc, mswPalette, 0 );
            RealizePalette( b->hDc );
        }
        b->wFactor = (double)GetDeviceCaps( b->hDc, LOGPIXELSX );
        b->hFactor = (double)GetDeviceCaps( b->hDc, LOGPIXELSY );
        double dpi;
        /** @prefs [Preference] ScreenDPI=96.0 Sets DPI of screen */
        wPrefGetFloat(PREFSECTION, DPISET, &dpi, 96.0);
        b->DPI = dpi;
        b->hWnd = hWnd;
        SetROP2( b->hDc, R2_WHITE );
        Rectangle( b->hDc, 0, 0, b->w, b->h );
        if ( (b->option & BD_DIRECT) == 0 ) {
            SetROP2( hDc, R2_WHITE );
            Rectangle( hDc, 0, 0, b->w, b->h );
            ReleaseDC( hWnd, hDc );
        }
        break;
    case WM_SIZE:
        b = (wDraw_p)mswMapIndex( inx );
        ix = LOWORD( lParam );
        iy = HIWORD( lParam );
        b->w = ix+2;
        b->h = iy+2;
        if (b->hWnd) {
            if ( b->option & BD_DIRECT ) {
            } else {
                hDc = GetDC( b->hWnd );
//-			DeleteObject( b->hBmOld );
                DeleteObject( b->hBmMain );
                DeleteObject( b->hBmTemp );
                b->hBmMain = CreateCompatibleBitmap( hDc, b->w, b->h );
                b->hBmTemp = CreateCompatibleBitmap( hDc, b->w, b->h );
//-			b->hBmOld = SelectObject( b->hDc, b->hBmMain );
                ReleaseDC( b->hWnd, hDc );
                SetROP2( b->hDc, R2_WHITE );
                Rectangle( b->hDc, 0, 0, b->w, b->h );
            }
        }
        /*if (b->drawResize)
        	b->drawResize( b, b->size );*/
        if (b->drawRepaint) {
            b->drawRepaint( b, b->data, 0, 0 );
        }
        return (LRESULT)0;
    case WM_MOUSEMOVE:
        activeWnd = GetActiveWindow();
        focusWnd = GetFocus();
        if (focusWnd != hWnd) {
            b = (wDraw_p)mswMapIndex( inx );
            if (!b) {
                break;
            }
            if ( !((wControl_p)b->parent) ) {
                break;
            }
            if ( ((wControl_p)b->parent)->hWnd != activeWnd ) {
                break;
            }
        }
    case WM_LBUTTONDOWN:
    case WM_LBUTTONUP:
    case WM_RBUTTONDOWN:
    case WM_RBUTTONUP:
    case WM_LBUTTONDBLCLK:
    case WM_MBUTTONUP:
    case WM_MBUTTONDOWN:
        if (message == WM_LBUTTONDOWN) {
            action = wActionLDown;
        } else if (message == WM_RBUTTONDOWN) {
            action = wActionRDown;
        } else if (message == WM_LBUTTONUP) {
            action = wActionLUp;
        } else if (message == WM_RBUTTONUP) {
            action = wActionRUp;
        } else if (message == WM_MBUTTONUP) {
            action = wActionMUp;
        } else if (message == WM_MBUTTONDOWN) {
            action = wActionMDown;
        } else if (message == WM_LBUTTONDBLCLK) {
            action = wActionLDownDouble;
        } else {
            if ( (wParam & MK_LBUTTON) != 0) {
                action = wActionLDrag;
            } else if ( (wParam & MK_RBUTTON) != 0) {
                action = wActionRDrag;
            } else if ( (wParam & MK_MBUTTON) != 0) {
                action = wActionMDrag;
            } else {
                action = wActionMove;
            }
        }
        b = (wDraw_p)mswMapIndex( inx );
        if (!b) {
            break;
        }
        if (doSetFocus && message != WM_MOUSEMOVE) {
            SetFocus( ((wControl_p)b->parent)->hWnd );
        }
        if ( (b->option&BD_NOCAPTURE) == 0 ) {
            if (message == WM_LBUTTONDOWN || message == WM_RBUTTONDOWN) {
                SetCapture( b->hWnd );
            } else if (message == WM_LBUTTONUP || message == WM_RBUTTONUP) {
                ReleaseCapture();
            }
        }
        ix = LOWORD( lParam );
        iy = HIWORD( lParam );
        x = XWINPIX2DRAWPIX( b, ix );
        y = YWINPIX2DRAWPIX( b, iy );
        b->lastX = x;
        b->lastY = y;
        if (b->action) {
            b->action( b, b->data, action, x, y );
        }
        if (b->hWnd) {
            UpdateWindow(b->hWnd);
        }
        return (LRESULT)0;
    case WM_CHAR:
        b = (wDraw_p)mswMapIndex( inx );
        extChar = wAccelKey_None;
        if (lParam & 0x01000000L)
            switch( wParam ) {
            case VK_DELETE:
                extChar = wAccelKey_Del;
                break;
            case VK_INSERT:
                extChar = wAccelKey_Ins;
                break;
            case VK_HOME:
                extChar = wAccelKey_Home;
                break;
            case VK_END:
                extChar = wAccelKey_End;
                break;
            case VK_PRIOR:
                extChar = wAccelKey_Pgup;
                break;
            case VK_NEXT:
                extChar = wAccelKey_Pgdn;
                break;
            case VK_UP:
                extChar = wAccelKey_Up;
                break;
            case VK_DOWN:
                extChar = wAccelKey_Down;
                break;
            case VK_RIGHT:
                extChar = wAccelKey_Right;
                break;
            case VK_LEFT:
                extChar = wAccelKey_Left;
                break;
            case VK_BACK:
                extChar = wAccelKey_Back;
                break;
            case VK_F1:
                extChar = wAccelKey_F1;
                break;
            case VK_F2:
                extChar = wAccelKey_F2;
                break;
            case VK_F3:
                extChar = wAccelKey_F3;
                break;
            case VK_F4:
                extChar = wAccelKey_F4;
                break;
            case VK_F5:
                extChar = wAccelKey_F5;
                break;
            case VK_F6:
                extChar = wAccelKey_F6;
                break;
            case VK_F7:
                extChar = wAccelKey_F7;
                break;
            case VK_F8:
                extChar = wAccelKey_F8;
                break;
            case VK_F9:
                extChar = wAccelKey_F9;
                break;
            case VK_F10:
                extChar = wAccelKey_F10;
                break;
            case VK_F11:
                extChar = wAccelKey_F11;
                break;
            case VK_F12:
                extChar = wAccelKey_F12;
                break;
            }
        if (b && b->action) {
            if (extChar != wAccelKey_None) {
                b->action( b, b->data, wActionExtKey + ( (int)extChar << 8 ), b->lastX,
                           b->lastY );
            } else {
                b->action( b, b->data, wActionText + ( (int)wParam << 8 ), b->lastX,
                           b->lastY );
            }
        }
        return (LRESULT)0;

    case WM_PAINT:
        b = (wDraw_p)mswMapIndex( inx );
        if (b && b->type == B_DRAW) {
            if (GetUpdateRect( b->hWnd, &rect, FALSE )) {
                hDc = BeginPaint( hWnd, &ps );
                if ( b->hasPalette ) {
                    int winPaletteClock = mswGetPaletteClock();
                    if ( b->paletteClock < winPaletteClock ) {
                        RealizePalette( hDc );
                        b->paletteClock = winPaletteClock;
                    }
                }
                HBITMAP hBmOld = SelectObject( b->hDc, b->hBmMain );

                if (bDrawMainBM) {
                    BitBlt(hDc, rect.left, rect.top,
                           rect.right - rect.left, rect.bottom - rect.top,
                           b->hDc, rect.left, rect.top,
                           SRCCOPY);
                }
                SelectObject( b->hDc, b->bCopiedMain?b->hBmTemp:b->hBmMain );
                BitBlt( hDc, rect.left, rect.top,
                        rect.right-rect.left, rect.bottom-rect.top,
                        b->hDc, rect.left, rect.top,
                        bDrawMainBM?SRCAND:SRCCOPY);
                SelectObject( b->hDc, hBmOld );
                EndPaint( hWnd, &ps );
                b->bCopiedMain = FALSE;
            }
        }
        break;
    case WM_DESTROY:
        b = (wDraw_p)mswMapIndex( inx );
        if (b && b->type == B_DRAW) {
            if (b->hDc) {
                DeleteDC( b->hDc );
                b->hDc = (HDC)0;
            }
            if (b->hDcBackup) {
                DeleteDC( b->hDcBackup );
                b->hDcBackup = (HDC)0;
            }
        }
        break;

    case WM_SETCURSOR:
        // Set cursor based on wSetCursor
        DoSetCursor();
        // return TRUE to suppress my parent from overriding me
        return TRUE;

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


static LRESULT drawMsgProc( wDraw_p b, HWND hWnd, UINT message, WPARAM wParam,
                            LPARAM lParam )
{
    wAction_t action;

    switch( message ) {
    case WM_MOUSEWHEEL:
        /* handle mouse wheel events */
        if (GET_KEYSTATE_WPARAM(wParam) & (MK_SHIFT|MK_MBUTTON) ) {
            if (GET_KEYSTATE_WPARAM(wParam) & MK_CONTROL ) {
                if (GET_WHEEL_DELTA_WPARAM(wParam) > 0) {
                    action = wActionScrollLeft;
                } else {
                    action = wActionScrollRight;
                }
            } else {
                if (GET_WHEEL_DELTA_WPARAM(wParam) > 0) {
                    action = wActionScrollUp;
                } else {
                    action = wActionScrollDown;
                }
            }
        } else {
            if (GET_WHEEL_DELTA_WPARAM(wParam) > 0) {
                action = wActionWheelUp;
            } else {
                action = wActionWheelDown;
            }
        }
        if (b->action) {
            b->action( b, b->data, action, b->lastX, b->lastY );
        }
        return (LRESULT)0;
    case WM_MOUSEHWHEEL:
        if ( GET_KEYSTATE_WPARAM(wParam) & (MK_SHIFT|MK_MBUTTON)) {
            if ( GET_WHEEL_DELTA_WPARAM(wParam) > 0 ) {
                action = wActionScrollRight;
            } else {
                action = wActionScrollLeft;
            }
        }
        if (b->action) {
            b->action( b, b->data, action, b->lastX, b->lastY );
        }
        return (LRESULT)0;
    }

    return DefWindowProc( hWnd, message, wParam, lParam );
}


static void drawDoneProc( wControl_p b )
{
    wDraw_p d = (wDraw_p)b;
    if (d->hBmMain) {
        SelectObject( d->hDc, d->hBmOld );
        DeleteObject( d->hBmMain );
        d->hBmMain = (HBITMAP)0;
        DeleteObject( d->hBmTemp );
        d->hBmTemp = (HBITMAP)0;
    }
    if (d->hPen) {
        SelectObject( d->hDc, GetStockObject( BLACK_PEN ) );
        DeleteObject( d->hPen );
        d->hPen = (HPEN)0;
    }
    if (d->hBrush) {
        SelectObject( d->hDc, GetStockObject( BLACK_BRUSH) );
        DeleteObject( d->hBrush );
        d->hBrush = (HBRUSH)0;
    }
    if (d->hDc) {
        DeleteDC( d->hDc );
        d->hDc = (HDC)0;
    }
    if ( d->hDcBackup ) {
        DeleteDC( d->hDcBackup );
        d->hDcBackup = (HDC)0;
    }
    while (bmRoot) {
        if (bmRoot->bm) {
            DeleteObject( bmRoot->bm );
        }
        bmRoot = bmRoot->next;
    }
}


static callBacks_t drawCallBacks = {
    NULL,
    drawDoneProc,
    (messageCallback_p)drawMsgProc
};

static wDraw_p drawList = NULL;


void mswRedrawAll( void )
{
    wDraw_p p;
    for ( p=drawList; p; p=p->drawNext ) {
        if (p->drawRepaint) {
            p->drawRepaint( p, p->data, 0, 0 );
        }
    }
}


void mswRepaintAll( void )
{
    wDraw_p b;
    HDC hDc;
    RECT rect;
    PAINTSTRUCT ps;

    for ( b=drawList; b; b=b->drawNext ) {
        if (GetUpdateRect( b->hWnd, &rect, FALSE )) {
            hDc = BeginPaint( b->hWnd, &ps );
            HBITMAP hBmOld = SelectObject( b->hDc, b->hBmMain );
            BitBlt( hDc, rect.left, rect.top,
                    rect.right-rect.left, rect.bottom-rect.top,
                    b->hDc, rect.left, rect.top,
                    SRCCOPY );
            SelectObject( b->hDc, b->hBmTemp );
            BitBlt( hDc, rect.left, rect.top,
                    rect.right-rect.left, rect.bottom-rect.top,
                    b->hDc, rect.left, rect.top,
                    SRCAND );
            SelectObject( b->hDc, hBmOld );
            EndPaint( b->hWnd, &ps );
        }
    }
}


wDraw_p wDrawCreate(
    wWin_p parent,
    wWinPix_t x,
    wWinPix_t y,
    const char * helpStr,
    long option,
    wWinPix_t w,
    wWinPix_t h,
    void * data,
    wDrawRedrawCallBack_p redrawProc,
    wDrawActionCallBack_p action )
{
    wDraw_p d;
    RECT rect;
    int index;
    HDC hDc;

    d = mswAlloc( parent, B_DRAW, NULL, sizeof *d, data, &index );
    mswComputePos( (wControl_p)d, x, y );
    d->w = w;
    d->h = h;
    d->drawRepaint = NULL;
    d->action = action;
    d->option = option;

    d->hWnd = CreateWindow( mswDrawWindowClassName, NULL,
                            WS_CHILDWINDOW|WS_VISIBLE|WS_BORDER,
                            d->x, d->y, w, h,
                            ((wControl_p)parent)->hWnd, (HMENU)(UINT_PTR)index, mswHInst, NULL );

    if (d->hWnd == (HWND)0) {
        mswFail( "CreateWindow(DRAW)" );
        return d;
    }

    GetWindowRect( d->hWnd, &rect );

    d->w = rect.right - rect.left;
    d->h = rect.bottom - rect.top;
    d->drawRepaint = redrawProc;
    /*if (d->drawRepaint)
    	d->drawRepaint( d, d->data, 0.0, 0.0 );*/

    mswAddButton( (wControl_p)d, FALSE, helpStr );
    mswCallBacks[B_DRAW] = &drawCallBacks;
    d->drawNext = drawList;
    drawList = d;
    if (mswPalette) {
        hDc = GetDC( d->hWnd );
        d->hasPalette = TRUE;
        SelectPalette( hDc, mswPalette, 0 );
        ReleaseDC( d->hWnd, hDc );
    }
    d->bCopiedMain = FALSE;
    return d;
}

/*
 *****************************************************************************
 *
 * Bitmaps
 *
 *****************************************************************************
 */

wDraw_p wBitMapCreate( wWinPix_t w, wWinPix_t h, int planes )
{
    wDraw_p d;
    HDC hDc;

    d = (wDraw_p)calloc(1,sizeof *d);
    d->type = B_DRAW;
    d->shown = TRUE;
    d->x = 0;
    d->y = 0;
    d->w = w;
    d->h = h;
    d->drawRepaint = NULL;
    d->action = NULL;
    d->option = 0;

    hDc = GetDC(mswHWnd);
    d->hDc = CreateCompatibleDC( hDc );
    if ( d->hDc == (HDC)0 ) {
        wNoticeEx( NT_ERROR, "CreateBitMap: CreateDC fails", "Ok", NULL );
        return FALSE;
    }
    d->hBmMain = CreateCompatibleBitmap( hDc, d->w, d->h );
    if ( d->hBmMain == (HBITMAP)0 ) {
        wNoticeEx( NT_ERROR, "CreateBitMap: CreateBM Main fails", "Ok", NULL );
        ReleaseDC(mswHWnd, hDc);
        return FALSE;
    }
    d->hBmTemp = CreateCompatibleBitmap( hDc, d->w, d->h );
    if ( d->hBmTemp == (HBITMAP)0 ) {
        wNoticeEx( NT_ERROR, "CreateBitMap: CreateBM Temp fails", "Ok", NULL );
        ReleaseDC(mswHWnd, hDc);
        return FALSE;
    }
    d->hasPalette = (GetDeviceCaps(hDc,RASTERCAPS ) & RC_PALETTE) != 0;
    ReleaseDC( mswHWnd, hDc );
    d->hBmOld = SelectObject( d->hDc, d->hBmMain );
    if (mswPalette) {
        SelectPalette( d->hDc, mswPalette, 0 );
        RealizePalette( d->hDc );
    }
    d->wFactor = (double)GetDeviceCaps( d->hDc, LOGPIXELSX );
    d->hFactor = (double)GetDeviceCaps( d->hDc, LOGPIXELSY );
    d->DPI = 96.0; /*min( d->wFactor, d->hFactor );*/
    d->hWnd = 0;
    wDrawClear(d);
//-	SetROP2( d->hDc, R2_WHITE );
//-	Rectangle( d->hDc, 0, 0, d->w, d->h );
    return d;
}

wBool_t wBitMapDelete( wDraw_p d )
{
    if (d->hPen) {
        SelectObject( d->hDc, GetStockObject( BLACK_PEN ) );
        DeleteObject( d->hPen );
        d->hPen = (HPEN)0;
    }
    if (d->hBmMain) {
        SelectObject( d->hDc, d->hBmOld );
        DeleteObject( d->hBmMain );
        d->hBmMain = (HBITMAP)0;
        DeleteObject( d->hBmTemp );
        d->hBmTemp = (HBITMAP)0;
    }
    if (d->hDc) {
        DeleteDC( d->hDc );
        d->hDc = (HDC)0;
    }
    free(d);
    return TRUE;
}

/**
 * write bitmap file. The bitmap in d must contain a valid HBITMAP
 *
 * \param  d	    A wDraw_p to process.
 * \param  fileName Filename of the file.
 *
 * \returns A wBool_t. TRUE on success
 */

wBool_t
wBitMapWriteFile(wDraw_p d, const char * fileName)
{
    FIBITMAP *dib = NULL;
    FIBITMAP *dib2 = NULL;
    FREE_IMAGE_FORMAT fif = FIF_UNKNOWN;
    BOOL bSuccess = FALSE;

    if (d->hBmMain) {

        BITMAP bm;
        GetObject(d->hBmMain, sizeof(BITMAP), (LPSTR)&bm);
        dib = FreeImage_Allocate(bm.bmWidth, bm.bmHeight, bm.bmBitsPixel, 0, 0, 0);
        // The GetDIBits function clears the biClrUsed and biClrImportant BITMAPINFO members (dont't know why)
        // So we save these infos below. This is needed for palettized images only.
        int nColors = FreeImage_GetColorsUsed(dib);
        HDC dc = GetDC(NULL);
        GetDIBits(dc,
                  d->hBmMain,
                  0,
                  FreeImage_GetHeight(dib),
                  FreeImage_GetBits(dib),
                  FreeImage_GetInfo(dib),
                  DIB_RGB_COLORS);
        ReleaseDC(NULL, dc);

        // restore BITMAPINFO members
        FreeImage_GetInfoHeader(dib)->biClrUsed = nColors;
        FreeImage_GetInfoHeader(dib)->biClrImportant = nColors;
        // we will get a 32 bit bitmap on Windows systems with invalid alpha
        // so it needs to be converted to 24 bits.
        // (see: https://sourceforge.net/p/freeimage/discussion/36110/thread/0699ce8e/ )
        dib2 = FreeImage_ConvertTo24Bits(dib);
        FreeImage_Unload(dib);
    }

    // Try to guess the file format from the file extension
    fif = FreeImage_GetFIFFromFilename(fileName);
    if (fif != FIF_UNKNOWN) {
        // Check that the dib can be saved in this format
        BOOL bCanSave;

        FREE_IMAGE_TYPE image_type = FreeImage_GetImageType(dib2);
        if (image_type == FIT_BITMAP) {
            // standard bitmap type
            WORD bpp = FreeImage_GetBPP(dib2);
            bCanSave = (FreeImage_FIFSupportsWriting(fif) &&
                        FreeImage_FIFSupportsExportBPP(fif, bpp));
        } else {
            // special bitmap type
            bCanSave = FreeImage_FIFSupportsExportType(fif, image_type);
        }

        if (bCanSave) {
            int flags;

            switch (fif) {
            case FIF_JPEG:
                flags = JPEG_QUALITYNORMAL;
                break;
            case FIF_PNG:
                flags = PNG_DEFAULT;
                break;
            default:
                flags = 0;		// whatver the default is for the file format
            }
            bSuccess = FreeImage_Save(fif, dib2, fileName, flags);
        }
    }
    FreeImage_Unload(dib2);

    return bSuccess;
}

