//---------------------------------------------------------------------------
//		Copyright (C) 1991-93, Microsoft Corporation
//
// You have a royalty-free right to use, modify, reproduce and distribute
// the Sample Custom Control Files (and/or any modified version) in any way
// you find useful, provided that you agree that Microsoft has no warranty,
// obligation or liability for any Custom Control File.
//---------------------------------------------------------------------------
// Cntr.c
//---------------------------------------------------------------------------
// Cntr Display Control
//---------------------------------------------------------------------------

#include <windows.h>
#include <vbapi.h>
#include "cntr.h"

//---------------------------------------------------------------------------
// Global Variables
//---------------------------------------------------------------------------
HANDLE hmodDLL;


//---------------------------------------------------------------------------
// Standard Error Values
//---------------------------------------------------------------------------
#define ERR_None	      0
#define ERR_InvPropVal	    380     // Error$(380) = "Invalid property value"


//---------------------------------------------------------------------------
// Local Prototypes
//---------------------------------------------------------------------------
VOID NEAR DeleteBitMaps(BPCNTR bpcntr);
VOID NEAR PaintCntr(BPCNTR bpcntr, HWND hwnd, HDC hdc);
ERR  NEAR ErrSetCntrVal(BPCNTR bpcntr, HWND hwnd, float Val);
VOID NEAR SizeCntr(BPCNTR bpcntr, HWND hwnd);


//---------------------------------------------------------------------------
// Cntr Control Procedure
//---------------------------------------------------------------------------
LONG FAR PASCAL _export CntrCtlProc
(
    HCTL   hctl,
    HWND   hwnd,
    USHORT msg,
    USHORT wp,
    LONG   lp
)
{
    BPCNTR bpcntr = NULL;

    segCntr = (_segment)hctl;

    // Message pre-processing
    switch (msg)
	{
	case WM_NCDESTROY:
	    bpcntr = BpcntrDEREF(hctl);
	    DeleteBitMaps(bpcntr);
	    break;

	case WM_NCCREATE:
	    bpcntr = BpcntrDEREF(hctl);
	    bpcntr->hFont = NULL;
	    ErrSetCntrVal(bpcntr, hwnd, (float)0.0);
	    bpcntr->DigitsLeft = 3;
	    bpcntr->DigitsRight = 0;
	    bpcntr->colorBackRight = COLOR_HIGHLIGHT | 0x80000000;
	    bpcntr->colorForeRight = COLOR_HIGHLIGHTTEXT | 0x80000000;
	    break;

	case WM_GETMINMAXINFO:
	    ((LPPOINT)lp)[3].x = 4;
	    ((LPPOINT)lp)[3].y = 4;
	    return 1L;

	case WM_SIZE:
	    SizeCntr(BpcntrDEREF(hctl), hwnd);
	    break;

	case WM_SETFONT:
	    bpcntr = BpcntrDEREF(hctl);
	    bpcntr->hFont = (HFONT)wp;
	    DeleteBitMaps(bpcntr);
	    break;

	case WM_GETFONT:
	    return BpcntrDEREF(hctl)->hFont;

	case WM_ERASEBKGND:
	    return TRUE;

	case WM_PAINT:
	    if (wp)
		PaintCntr(BpcntrDEREF(hctl), hwnd, (HDC)wp);
	    else
		{
		PAINTSTRUCT ps;

		BeginPaint(hwnd, &ps);
		PaintCntr(BpcntrDEREF(hctl), hwnd, ps.hdc);
		EndPaint(hwnd, &ps);
		}
	    break;

	case VBM_SETPROPERTY:
	    switch (wp)
		{
		case IPROP_CNTR_DIGITSLEFT:
		    if ((SHORT)lp < 1 || (SHORT)lp > 6)
			return ERR_InvPropVal;
		    bpcntr = BpcntrDEREF(hctl);
		    bpcntr->DigitsLeft = (SHORT)lp;
		    SizeCntr(bpcntr, hwnd);
		    InvalidateRect(hwnd, NULL, FALSE);
		    return ERR_None;

		case IPROP_CNTR_DIGITSRIGHT:
		    if ((SHORT)lp < 0 || (SHORT)lp > 5)
			return ERR_InvPropVal;
		    bpcntr = BpcntrDEREF(hctl);
		    bpcntr->DigitsRight = (SHORT)lp;
		    SizeCntr(bpcntr, hwnd);
		    InvalidateRect(hwnd, NULL, FALSE);
		    // reset value to reevaluate roll factors
		    ErrSetCntrVal(BpcntrDEREF(hctl), hwnd, bpcntr->Value);
		    return ERR_None;

		case IPROP_CNTR_VALUE:
		    return ErrSetCntrVal(BpcntrDEREF(hctl), hwnd,
					 *(float FAR *)&(lp));

		case IPROP_CNTR_BACKCOLORRIGHT:
		case IPROP_CNTR_FORECOLORRIGHT:
		case IPROP_CNTR_BACKCOLOR:
		case IPROP_CNTR_FORECOLOR:
		    InvalidateRect(hwnd, NULL, FALSE);
		    DeleteBitMaps(BpcntrDEREF(hctl));
		    return ERR_None;
		}
	    break;
	}

    // Default processing:
    return VBDefControlProc(hctl, hwnd, msg, wp, lp);
}


//---------------------------------------------------------------------------
// Delete currently cached bitmaps.  Called when control size, font, or
// color has changed.  Bitmaps will be recalculated next time they are
// needed.
//---------------------------------------------------------------------------
VOID NEAR DeleteBitMaps
(
    BPCNTR bpcntr
)
{
    if (bpcntr->hbmp[0])
	{
	DeleteObject(bpcntr->hbmp[0]);
	bpcntr->hbmp[0] = NULL;
	}
    if (bpcntr->hbmp[1])
	{
	DeleteObject(bpcntr->hbmp[1]);
	bpcntr->hbmp[1] = NULL;
	}
}


//---------------------------------------------------------------------------
// Caluculate new bitmaps.  We will create and paint a new bitmap which looks
// like:
//	-+
//	0|
//	-+
//	1|
//	-+
//	2|
//	-+
//	:
//	-+
//	9|
//	-+
//	0|
//	-+
// using the correct colors and font.
//---------------------------------------------------------------------------
HBITMAP NEAR GetBitMap
(
    BPCNTR bpcntr,
    HWND   hwnd,
    SHORT  inx
)
{
    static  CHAR szDig[] = "01234567890";
    RECT    rcClient;
    RECT    rcDigit;
    SHORT   digit;
    SHORT   DigitHeight;
    HDC     hdcScr;
    HDC     hdcMem;
    HBITMAP hbmp;
    HBITMAP hbmpOld;
    HBRUSH  hbr;
    HFONT   hfontOld = NULL;

    if (bpcntr->hbmp[inx])
	return bpcntr->hbmp[inx];
    GetClientRect(hwnd, &rcClient);

    DigitHeight = rcClient.bottom + 1;
    rcDigit = rcClient;
    rcDigit.right = rcClient.right = bpcntr->DigitWidth - 1;
    rcClient.bottom = DigitHeight * 11;
    hdcScr = GetDC(NULL);
    hdcMem = CreateCompatibleDC(hdcScr);
    hbmp = CreateCompatibleBitmap(hdcScr, rcClient.right+1, rcClient.bottom+1);
    ReleaseDC(NULL, hdcScr);
    if (!hbmp)
	{
	DeleteDC(hdcMem);
	return NULL;
	}
    bpcntr->hbmp[inx] = hbmp;
    hbmpOld = SelectObject(hdcMem, hbmp);
    // set up colors, etc.
    if (!inx)
	hbr = (HBRUSH)SendMessage(GetParent(hwnd), WM_CTLCOLOR, hdcMem, MAKELONG(hwnd, 0));
    else
	{
	LONG BackColor = RGBCOLOR(bpcntr->colorBackRight);
	LONG ForeColor = RGBCOLOR(bpcntr->colorForeRight);

	hbr = CreateSolidBrush(BackColor);
	SetTextColor(hdcMem, ForeColor);
	}
    if (hbr)
	{
	FillRect(hdcMem, &rcClient, hbr);
	if (inx)
	    DeleteObject(hbr);
	}
    SetBkMode(hdcMem, TRANSPARENT);
    if (bpcntr->hFont)
	hfontOld = SelectObject(hdcMem, bpcntr->hFont);
    MoveTo(hdcMem, rcClient.right, 0);
    LineTo(hdcMem, rcClient.right, rcClient.bottom);

    for (digit = 0; digit <= 10; ++digit)
	{
	MoveTo(hdcMem, 0, rcDigit.bottom);
	LineTo(hdcMem, rcDigit.right, rcDigit.bottom);
	DrawText(hdcMem, &szDig[digit], 1, &rcDigit,
		 DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_NOCLIP);
	rcDigit.top += DigitHeight;
	rcDigit.bottom += DigitHeight;
	}
    if (hfontOld)
	SelectObject(hdcMem, hfontOld);
    SelectObject(hdcMem, hbmpOld);
    DeleteDC(hdcMem);
    return bpcntr->hbmp[inx];
}


//---------------------------------------------------------------------------
// Handle the resizing.  Calculate new width for digits and the size of the
// gap.  Delete the old bitmaps, so that new ones will be created on the next
// paint.
//---------------------------------------------------------------------------
VOID NEAR SizeCntr
(
    BPCNTR bpcntr,
    HWND   hwnd
)
{
    RECT  rcClient;
    SHORT digits;

    GetClientRect(hwnd, &rcClient);
    digits = bpcntr->DigitsLeft + bpcntr->DigitsRight;
    bpcntr->DigitWidth = (rcClient.right + 1) / digits;
    bpcntr->Gap = rcClient.right + 1 - digits * bpcntr->DigitWidth;
    DeleteBitMaps(bpcntr);
}


//---------------------------------------------------------------------------
// Handle setting a new value.
//---------------------------------------------------------------------------
ERR NEAR ErrSetCntrVal
(
    BPCNTR bpcntr,
    HWND   hwnd,
    float  Val
)
{
    LONG  ValInt;
    LONG  ValFrac;
    SHORT digit;
    SHORT last;
    SHORT StartRoll;
    CHAR  szVal[13];

    if (Val < (float)0.0)
	return ERR_InvPropVal;
    if (Val > (float)999999.0)
	return ERR_InvPropVal;
    bpcntr->Value = Val;
    ValInt = (LONG)Val;
    ValFrac = (LONG)((Val - (float)ValInt) * 1000000.0);
    wsprintf(szVal, "%06ld%06ld", ValInt, ValFrac);
    last = 6 + bpcntr->DigitsRight;
    StartRoll = last - 1;
    for (digit = last; digit >= 6 - bpcntr->DigitsLeft; --digit)
	if (digit == StartRoll - 1 && szVal[StartRoll] == '9')
	    --StartRoll;
    bpcntr->StartRoll = StartRoll;
    bpcntr->RollVal = (szVal[last] - '0') * 10;
    if (last < 11)
	bpcntr->RollVal += (szVal[last+1] - '0');
    lstrcpy(bpcntr->szVal, szVal);
    InvalidateRect(hwnd, NULL, FALSE);
    return ERR_None;
}


//---------------------------------------------------------------------------
// Handle painting.  Blit the correct portion of the stashed bitmaps for
// each digit.
//---------------------------------------------------------------------------
VOID NEAR PaintCntr
(
    BPCNTR bpcntr,
    HWND   hwnd,
    HDC    hdc
)
{
    RECT    rcDigit;
    SHORT   digit;
    HBRUSH  hbr;
    SHORT   roll;
    SHORT   top;
    HDC     hdcMem;
    HBITMAP hbmp;
    HBITMAP hbmpOld = NULL;

    hdcMem = CreateCompatibleDC(hdc);
    if (!hdcMem)
	return;

    // size the situation
    GetClientRect(hwnd, &rcDigit);

    hbr = (HBRUSH)SendMessage(GetParent(hwnd), WM_CTLCOLOR, hdc, MAKELONG(hwnd, 0));
    rcDigit.right = bpcntr->Gap;
    FillRect(hdc, &rcDigit, hbr);
    rcDigit.left  = rcDigit.right;
    rcDigit.right = rcDigit.left + bpcntr->DigitWidth;

    roll = (bpcntr->RollVal * rcDigit.bottom) / 100;
    for (digit = 6 - bpcntr->DigitsLeft; digit <= 5 + bpcntr->DigitsRight; ++digit)
	{
	hbmp = GetBitMap(bpcntr, hwnd, digit >= 6);
	if (!hbmp)
	    {
	    if (hbmpOld)
		SelectObject(hdcMem, hbmpOld);
	    DeleteDC(hdcMem);
	    return;
	    }
	hbmpOld = SelectObject(hdcMem, hbmp);
	if (RectVisible(hdc, &rcDigit))
	    {
	    top = (bpcntr->szVal[digit] - '0') * (rcDigit.bottom + 1);
	    if (digit >= bpcntr->StartRoll)
		top += roll;
	    BitBlt(hdc, rcDigit.left, 0, bpcntr->DigitWidth, rcDigit.bottom + 1,
		   hdcMem, 0, top, SRCCOPY);
	    }
	rcDigit.left  = rcDigit.right;
	rcDigit.right = rcDigit.left + bpcntr->DigitWidth;
	}
    if (hbmpOld)
	SelectObject(hdcMem, hbmpOld);
    DeleteDC(hdcMem);
}


//---------------------------------------------------------------------------
// Register custom control.  This routine is called by VB when the custom
// control DLL is loaded for use.
//---------------------------------------------------------------------------
BOOL FAR PASCAL _export VBINITCC
(
    USHORT usVersion,
    BOOL   fRuntime
)
{
    // Avoid warnings on unused (but required) formal parameters
    fRuntime  = fRuntime;
    usVersion = usVersion;

    // Register control(s)
    return VBRegisterModel(hmodDLL, &modelCntr);
}


//---------------------------------------------------------------------------
// Provide custom control model information to host environment.
//---------------------------------------------------------------------------
LPMODELINFO FAR PASCAL _export VBGetModelInfo
(
    USHORT usVersion
)
{
    return &modelinfoCntr;
}


//---------------------------------------------------------------------------
// Initialize library. This routine is called when the first client loads
// the DLL.
//---------------------------------------------------------------------------
int FAR PASCAL LibMain
(
    HANDLE hModule,
    WORD   wDataSeg,
    WORD   cbHeapSize,
    LPSTR  lpszCmdLine
)
{
    // Avoid warnings on unused (but required) formal parameters
    wDataSeg	= wDataSeg;
    cbHeapSize	= cbHeapSize;
    lpszCmdLine = lpszCmdLine;

    hmodDLL = hModule;

    return 1;
}


//---------------------------------------------------------------------------
// WEP
//---------------------------------------------------------------------------
// C7 and QCWIN provide default a WEP:
//---------------------------------------------------------------------------
#if (_MSC_VER < 610)

int FAR PASCAL WEP(int fSystemExit);

//---------------------------------------------------------------------------
// For Windows 3.0 it is recommended that the WEP function reside in a
// FIXED code segment and be exported as RESIDENTNAME.  This is
// accomplished using the alloc_text pragma below and the related EXPORTS
// and SEGMENTS directives in the .DEF file.
//
// Read the comments section documenting the WEP function in the Windows
// 3.1 SDK "Programmers Reference, Volume 2: Functions" before placing
// any additional code in the WEP routine for a Windows 3.0 DLL.
//---------------------------------------------------------------------------
#pragma alloc_text(WEP_TEXT,WEP)

//---------------------------------------------------------------------------
// Performs cleanup tasks when the DLL is unloaded.  WEP() is
// called automatically by Windows when the DLL is unloaded (no
// remaining tasks still have the DLL loaded).	It is strongly
// recommended that a DLL have a WEP() function, even if it does
// nothing but returns success (1), as in this example.
//---------------------------------------------------------------------------
int FAR PASCAL WEP
(
    int fSystemExit
)
{
    // Avoid warnings on unused (but required) formal parameters
    fSystemExit = fSystemExit;

    return 1;
}
#endif // C6

//---------------------------------------------------------------------------
